source: trunk/Components/CanvasFontManager.pas@ 470

Last change on this file since 470 was 420, checked in by ataylor, 6 years ago

Improve DBCS string width calculations, other small tweaks.

  • Property svn:eol-style set to native
File size: 32.8 KB
RevLine 
[15]1Unit CanvasFontManager;
2
3Interface
4
5Uses
6 OS2Def,
[396]7 Classes,
8 Forms,
9 PMWIN,
10 Graphics;
[15]11
12Const
13 // This defines the fraction of a pixel that
14 // font character widths will be given in
15 FontWidthPrecisionFactor = 256;
16
17Type
18 // a user-oriented specification of a font;
19 // does not include OS/2 API data
20 TFontSpec = record
21 FaceName: string[ 64 ];
22 PointSize: integer; // if 0 then use x/y size
23 XSize: integer;
24 YSize: integer;
25 Attributes: TFontAttributes; // set of faBold, faItalic etc
26 end;
27
28 // NOTE: Char widths are in 1/FontWidthPrecisionFactor units
29 TCharWidthArray = array[ #0..#255 ] of longint;
30 TPCharWidthArray = ^ TCharWidthArray;
31
32 // Used internally for storing full info on font
33 TLogicalFont= Class( TComponent )
34 pFaceName: pstring; // user-selected name
35 pUseFaceName: pstring; // after substitutions.
36
37 // Selected bits of FONTMETRICS
38 fsSelection: USHORT;
39
40 FontType: TFontType;
41 FixedWidth: boolean;
42 PointSize: integer;
43 ID: integer;
44 Attributes: TFontAttributes;
45
46 // this can be nil if not already fetched
47 pCharWidthArray: TPCharWidthArray;
48 lMaxbaselineExt: LONG;
49 lAveCharWidth: LONG;
50 lMaxCharInc: LONG;
51 lMaxDescender: LONG;
[418]52 lEmInc: LONG;
[15]53
54 destructor Destroy; override;
55 end;
56
57 TFontFace = class
58 pName: pstring;
59 FixedWidth: boolean;
60 FontType: TFontType;
61
62 Sizes: TList; // relevant for bitmap fonts only
63 // contains TLogicalFont objects
64 constructor Create;
65 destructor Destroy; override;
66 end;
67
68 TCanvasFontManager = class
69 protected
70 FCanvas: TCanvas;
71 FLogicalFonts: TList;
72 FCurrentFontSpec: TFontSpec;
73 FDefaultFontSpec: TFontSpec;
74 FCurrentFont: TLogicalFont;
75 FAllowBitmapFonts: boolean;
76
77 protected
78 function CreateFont( const FontSpec: TFontSpec ): TLogicalFont;
79
80 function GetFont( const FontSpec: TFontSpec ): TLogicalFont;
81
82 procedure RegisterFont( Font: TLogicalFont );
83 procedure SelectFont( Font: TLogicalFont;
84 Scale: longint );
85
86 // Retrieve character widths for current font
87 procedure LoadMetrics;
88
89 // load metrics if needed
90 procedure EnsureMetricsLoaded;
91 public
92 Constructor Create( Canvas: TCanvas;
93 AllowBitmapFonts: boolean );
94 destructor Destroy; override;
95
96 // Useful functions:
97
98 // Set the font for the associated canvas.
99 procedure SetFont( const FontSpec: TFontSpec );
100
101 // Retrieve the width of the given char, in the current font
102 function CharWidth( const C: Char ): longint;
103
104 function AverageCharWidth: longint;
105 function MaximumCharWidth: longint;
[418]106 function CJKCharWidth: longint; // ALT
107 function CJKTextWidth( const Length: longint; const S: PChar ): longint;
[15]108 function IsFixed: boolean;
109
110 function CharHeight: longint;
111 function CharDescender: longint;
112
113 procedure DrawString( Var Point: TPoint;
114 const Length: longint;
115 const S: PChar );
116
117 property Canvas: TCanvas read FCanvas;
118 property DefaultFontSpec: TFontSpec read FDefaultFontSpec write FDefaultFontSpec;
119 end;
120
121// Convert a Sibyl font to a FontSpec (Color is left the same)
122procedure SibylFontToFontSpec( Font: TFont; Var FontSpec: TFontSpec );
123
124 // Thoughts on how it works....
125
126 // SelectFont looks for an existing logical font that
127 // matches the request. If found selects that logical font
128 // onto the canvas.
129
130 // If not found it creates a logical font and selects that onto
131 // the canvas.
132
133 // For bitmap fonts the logical font definition includes pointsize
134 // For outline fonts the defn is only face+attr; in this case
135 // selectfont also ses the 'CharBox' according to the point size.
136Implementation
137
138uses
[396]139 PMWin,
140 PMGpi,
141 OS2Def,
142 PmDev,
[15]143 SysUtils,
[403]144
[396]145 StringUtilsUnit;
[15]146
147Imports
148 Function GpiQueryCharStringPosAt( PS: Hps;
149 StartPoint: PPointL;
150 Options: ULONG;
151 Count: LONG;
152 TheString: PChar;
153 IncrementsArray: PLONG;
154 CharacterPoints: PPointL ): BOOL;
155 ApiEntry; 'PMGPI' Index 585;
156 Function GpiQueryCharStringPos( PS: Hps;
157 Options: ULONG;
158 Count: LONG;
159 TheString: PChar;
160 IncrementsArray: PLONG;
161 CharacterPoints: PPointL ): BOOL;
162 ApiEntry; 'PMGPI' Index 584;
163end;
164
165Type
166 // A little pretend window to send font name.size
167 // and get definite font info back. (See .CreateFont)
168 TFontWindow = class( TControl )
169 public
170 procedure CreateWnd; override;
171 property OwnerDraw;
172 Function SetPPFontNameSize( Const FNS: String ): Boolean;
173 end;
174
175var
176 FontFaces: TList = nil; // of TFontface
177 FontWindow: TFontWindow;
178
179 DefaultOutlineFixedFace: TFontFace;
180 DefaultOutlineProportionalFace: TFontFace;
181
182// TFontFace
183//------------------------------------------------------------------------
184
185constructor TFontface.Create;
186begin
187 Sizes := TList.Create;
188end;
189
190destructor TFontface.Destroy;
191begin
192 Sizes.Destroy;
193end;
194
195// TLogicalFont
196//------------------------------------------------------------------------
197
198// frees allocated memory, if any.
199// Note - does not delete the Gpi Logical Font
200destructor TLogicalFont.Destroy;
201begin
202 DisposeStr( pFaceName );
203 DisposeStr( pUseFaceName );
204
205 if pCharWidthArray <> nil then
206 FreeMem( pCharWidthArray,
207 sizeof( TCharWidthArray ) );
208
209 inherited Destroy;
210end;
211
212// TFontWindow
213//------------------------------------------------------------------------
214
215procedure TFontWindow.CreateWnd;
216begin
217 inherited CreateWnd;
218end;
219
220Function TFontWindow.SetPPFontNameSize( Const FNS: String ): Boolean;
221Var
222 CS: Cstring;
223Begin
224 CS := FNS;
225
226 Result := WinSetPresParam( Handle,
227 PP_FONTNAMESIZE,
228 Length( CS ) + 1,
229 CS );
230End;
231
232//------------------------------------------------------------------------
233
234// Convert a Sibyl font to a FontSpec
235//------------------------------------------------------------------------
236procedure SibylFontToFontSpec( Font: TFont; Var FontSpec: TFontSpec );
237begin
238 FontSpec.FaceName := Font.FaceName;
239 FontSpec.PointSize := Font.PointSize;
240 FontSpec.Attributes := Font.Attributes;
241end;
242
243// Find a font face with the given name
244//------------------------------------------------------------------------
245function FindFaceName( const name: string ): TFontFace;
246Var
247 FaceIndex: LongInt;
248 Face: TFontFace;
249begin
250 for FaceIndex := 0 to FontFaces.Count - 1 do
251 begin
252 Face := FontFaces[ FaceIndex ];
253
[396]254 if StrEqualIgnoringCase( Face.pName^, Name ) then
[15]255 begin
256 Result := Face;
257 exit;
258 end;
259 end;
260 Result := nil;
261end;
262
263// Return the first font face of type = Outline (scalable)
264//------------------------------------------------------------------------
265function GetFirstOutlineFace( FixedWidth: boolean ): TFontFace;
266Var
267 FaceIndex: LongInt;
268 Face: TFontFace;
269begin
270 for FaceIndex := 0 to FontFaces.Count - 1 do
271 begin
272 Face := FontFaces[ FaceIndex ];
273
274 if ( Face.FixedWidth = FixedWidth )
275 and ( Face.FontType = ftOutline ) then
276 begin
277 Result := Face;
278 exit;
279 end;
280 end;
281 Result := nil;
282end;
283
284// Find the bitmap font which best matches the given pointsize.
285//------------------------------------------------------------------------
286function GetClosestBitmapFixedFont( const PointSize: longint ): TLogicalFont;
287Var
288 FaceIndex: Longint;
289 FontIndex: longint;
290 Face: TFontFace;
291 Font: TLogicalFont;
292begin
293 Result := nil;
294 for FaceIndex := 0 to FontFaces.Count - 1 do
295 begin
296 Face := FontFaces[ FaceIndex ];
297
298 if Face.FontType = ftBitmap then
299 begin
300 for FontIndex := 0 to Face.Sizes.Count - 1 do
301 begin
302 Font := Face.Sizes[ FontIndex ];
303 if Font.FixedWidth then
304 begin
305 if ( Result = nil )
306 or ( Abs( Font.PointSize - PointSize )
307 < Abs( Result.PointSize - PointSize ) ) then
308 Result := Font;
309 end;
310 end;
311 end;
312 end;
313end;
314
315// Pick some nice default fonts.
316//------------------------------------------------------------------------
317procedure GetDefaultFonts;
318begin
319 // courier new is common and reasonably nice
320 DefaultOutlineFixedFace := FindFaceName( 'Courier New' );
321 if DefaultOutlineFixedFace = nil then
322 begin
323 DefaultOutlineFixedFace := GetFirstOutlineFace( true ); // first fixed outline face
324 end;
325
326 DefaultOutlineProportionalFace := FindFaceName( 'Helvetica' );
327 if DefaultOutlineProportionalFace = nil then
328 begin
329 DefaultOutlineProportionalFace := GetFirstOutlineFace( false ); // first prop outline face
330 end;
331end;
332
333Type
334 TMyFontMetrics = Array[ 0..1 ] Of FONTMETRICS;
335 PMyFontMetrics = ^TMyFontMetrics;
336
337// Fetch the global list of font faces and sizes
338//------------------------------------------------------------------------
339procedure GetFontList;
340Var
341 Count: LongInt;
342 aPS: HPS;
343 T: LongInt;
344 Font: TLogicalFont;
345 Face: TFontFace;
346 pfm: PMyFontMetrics;
347 FamilyName: string;
348 fsDefn: USHORT;
349Begin
350 FontFaces := TList.Create;
351
352 aPS := WinGetPS( HWND_DESKTOP );
353 Count := 0;
354 // Get font count
355 Count := GpiQueryFonts( aPS,
356 QF_PUBLIC,
357 Nil,
358 Count,
359 0,
360 Nil );
361 If Count > 0 Then
362 Begin
363 // allocate memory to retrieve all the font data.
364 GetMem( pfm, Count * SizeOf( FONTMETRICS ) );
365 GpiQueryFonts( aPS,
366 QF_PUBLIC,
367 Nil,
368 Count,
369 SizeOf(FONTMETRICS),
370 pfm^[ 0 ] );
371
372 For T := 0 To Count - 1 Do
373 Begin
374 Font := TLogicalFont.Create( Screen );
375
376 Font.pFaceName := NewStr( pfm^[ T ].szFaceName );
377 FamilyName := pfm^[ T ].szFamilyName;
378
379 // See what type it is. Actually this is not very
380 // useful as various substitutions are normally made.
381 fsDefn := pfm^[ T ].fsDefn;
382 If ( fsDefn And FM_DEFN_OUTLINE ) <> 0 Then
383 Font.FontType := ftOutline
384 else
385 Font.FontType := ftBitmap;
386
387 if Font.pFaceName^ = 'Helvetica' then
388 Font := Font;
389 Font.PointSize := pfm^[ T ].sNominalPointSize div 10;
390 Font.FixedWidth := pfm^[ T ].fsType And FM_TYPE_FIXED <> 0 ;
391 Font.fsSelection := pfm^[ T ].fsSelection;
392 Font.lMaxbaselineExt := pfm^[ T ].lMaxbaselineExt;
393 Font.lAveCharWidth := pfm^[ T ].lAveCharWidth;
394 Font.lMaxCharInc := pfm^[ T ].lMaxCharInc;
[418]395 Font.lEmInc := pfm^[ T ].lEmInc;
[15]396
397 Font.ID := -1; // and always shall be so...
398
399 Face := FindFaceName( Font.pFaceName^ );
400 if Face = nil then
401 begin
402 // new face found
403 Face := TFontFace.Create;
404 Face.pName := Font.pFaceName; // point to the actual face name string!
405 Face.FixedWidth := Font.FixedWidth;
406 Face.FontType := Font.FontType;
407 FontFaces.Add( Face );
408 end;
409 Face.Sizes.Add( Font );
410 End;
411 End;
412
413 FreeMem( pfm, Count * SizeOf( FONTMETRICS ) );
414 WinReleasePS( aPS );
415
416 // pick some for defaults
417 GetDefaultFonts;
418
419 FontWindow := TFontWindow.Create( Nil );
420 FontWindow.OwnerDraw := True;
421 FontWindow.CreateWnd;
422end;
423
424// Add .subscript to font name for attributes
425//------------------------------------------------------------------------
426Function ModifyFontName( const FontName: string;
427 const Attrs: TFontAttributes ): String;
428Begin
429 Result := FontName;
430 If faItalic in Attrs Then
431 Result := Result + '.Italic';
432 If faBold in Attrs Then
433 Result := Result + '.Bold';
434 If faOutline in Attrs Then
435 Result := Result + '.Outline';
436 If faStrikeOut in Attrs Then
437 Result := Result + '.Strikeout';
438 If faUnderScore in Attrs Then
439 Result := Result + '.Underscore';
440End;
441
442// Create a font without attributes
443//------------------------------------------------------------------------
444function CreateFontBasic( const FaceName: string;
445 const PointSize: integer ): TLogicalFont;
446var
447 PPString: string;
448 PresSpace: HPS;
449 FontInfo: FONTMETRICS;
450begin
451 Result := TLogicalFont.Create( nil );
452
453 if FindFaceName( FaceName ) = nil then
454 exit;
455
456 Result.PointSize := PointSize; // will use later if the result was an outline font...
457 Result.pFaceName := NewStr( FaceName );
458
459 // OK now we have found the font face...
460 PPString := IntToStr( PointSize) + '.' + FaceName;
461
462 PPString := ModifyFontName( PPString, [] );
463 If Not FontWindow.SetPPFontNameSize( PPString ) Then
464 // Gurk!
465 Exit;
466
467 PresSpace := WinGetPS( FontWindow.Handle );
468 If Not GpiQueryFontMetrics( PresSpace,
469 SizeOf( FONTMETRICS ),
470 FontInfo ) Then
471 begin
472 // Clurkle!?
473 WinReleasePS( PresSpace );
474 Exit;
475 end;
476 WinReleasePS( PresSpace );
477
478 if ( FontInfo.fsDefn And FM_DEFN_OUTLINE ) > 0 then
479 Result.FontType := ftOutline
480 else
481 Result.FontType := ftBitmap;
482end;
483
484// Provide outline substitutes for some common bitmap fonts
485// From Mozilla/2 source.
486//------------------------------------------------------------------------
487function SubstituteBitmapFontToOutline( const FaceName: string ): string;
488begin
[396]489 if StrEqualIgnoringCase( FaceName, 'Helv' ) then
[15]490 result := 'Helvetica'
[396]491 else if StrEqualIgnoringCase( FaceName, 'Tms Rmn' ) then
[15]492 result := 'Times New Roman'
[396]493 else if StrEqualIgnoringCase( FaceName, 'System Proportional' ) then
[15]494 result := 'Helvetica'
[396]495 else if StrEqualIgnoringCase( FaceName, 'System Monospaced' ) then
[15]496 result := 'Courier'
[396]497 else if StrEqualIgnoringCase( FaceName, 'System VIO' ) then
[15]498 result := 'Courier'
499 else
500 result := FaceName; // no substitution
501
502end;
503
504// NOTE!!! Not currently used or working...
505// Find a font with exact width and height
506//------------------------------------------------------------------------
507function FindXYSizeFont( const Face: TFontFace;
508 const XSize: longint;
509 const YSize: longint ): TLogicalFont;
510var
511 SizeIndex: longint;
512 F: TLogicalFont;
513 FontInfo: FONTMETRICS;
514begin
515 for SizeIndex := 0 to Face.Sizes.Count - 1 do
516 begin
517 F := Face.Sizes[ SizeIndex ];
518 if ( F.lMaxbaselineExt = YSize )
519 and ( F.lAveCharWidth = XSize ) then
520 begin
521 // found exact match
522 FontInfo.lMaxbaselineExt := F.lMaxbaselineExt;
523 FontInfo.lAveCharWidth := F.lAveCharWidth;
524 Result.FontType := ftBitmap;
525 end;
526 end;
527end;
528
529// Ask OS/2 dummy font window to convert a font spec
530// into a FONTMETRICS.
531//------------------------------------------------------------------------
532procedure AskOS2FontDetails( const FaceName: string;
533 const PointSize: longint;
534 const Attributes: TFontAttributes;
535 var FontInfo: FONTMETRICS );
536var
537 PPString: string;
538 PresSpace: HPS;
539begin
540 // Hack from Sibyl code - we don't know WTF the algorithm is
541 // for selecting between outline/bitmap and doing substitutions
542 // so send it to a dummy window and find out the resulting details
543 PPString := IntToStr( PointSize )
544 + '.'
545 + FaceName;
546
547 PPString := ModifyFontName( PPString, Attributes );
548
549 FontWindow.SetPPFontNameSize( PPString );
550
551 PresSpace := WinGetPS( FontWindow.Handle );
552 GpiQueryFontMetrics( PresSpace,
553 SizeOf( FontInfo ),
554 FontInfo );
555 WinReleasePS( PresSpace );
556end;
557
558// Look for the best match for the given face, size and attributes.
559// If FixedWidth is set then makes sure that the result is fixed
560// (if there is any fixed font on the system at all!)
561// This uses the OS/2 GPI and therefore makes some substitutions,
562// such as Helv 8 (bitmap) for Helvetica 8 (outline)
563//------------------------------------------------------------------------
564procedure FindBestFontMatch( const FaceName: string;
565 const PointSize: longint;
566 const Attributes: TFontAttributes;
567 const FixedWidth: boolean;
568 var FontInfo: FONTMETRICS );
569var
570 BestBitmapFontMatch: TLogicalFont;
571begin
572 // First just ask GPI to give us a font
573 AskOS2FontDetails( FaceName,
574 PointSize,
575 Attributes,
576 FontInfo );
577
578 if not FixedWidth then
579 // OK, whatever it gave us.
580 exit;
581
582 // we want a fixed width font...
583 if ( FontInfo.fsType and FM_TYPE_FIXED ) <> 0 then
584 // got a suitable font
585 exit;
586
587 // the stoopid freaking OS/2 GPI has given us
588 // a proportional font for that size
589 if DefaultOutlineFixedFace <> nil then
590 // use the default fixed width outline face
591 AskOS2FontDetails( DefaultOutlineFixedFace.pName^,
592 PointSize,
593 Attributes,
594 FontInfo );
595
596
597 if ( FontInfo.fsType and FM_TYPE_FIXED ) <> 0 then
598 // got a suitable font
599 exit;
600
601 // still got a proportional font,
602 // or we didn't have any fixed width outline face
603 // so see what we can find in the way of a bitmap fixed font
604
605 BestBitmapFontMatch := GetClosestBitmapFixedFont( PointSize );
606 if BestBitmapFontMatch <> nil then
607 begin
608 FontInfo.lMaxbaseLineExt := BestBitmapFontMatch.lMaxbaselineExt;
609 FontInfo.lAveCharWidth := BestBitmapFontMatch.lAveCharWidth;
610 FontInfo.fsDefn := 0;
611 FontInfo.szFaceName := BestBitmapFontMatch.pFaceName^;
612 end;
613 // else - there are no fixed fonts of any kind on the system. Oh dear.
614
615end;
616
617//------------------------------------------------------------------------
618// Font manager
619//------------------------------------------------------------------------
620
621// constructor
622//------------------------------------------------------------------------
623constructor TCanvasFontManager.Create( Canvas: TCanvas;
624 AllowBitmapFonts: boolean );
625begin
626 inherited Create;
627
628 if FontFaces = nil then
629 GetFontList;
630
631 FCanvas := Canvas;
632 FLogicalFonts := TList.Create;
633 FCurrentFontSpec.FaceName := 'notafont';
634 FCurrentFont := nil;
635 FAllowBitmapFonts := AllowBitmapFonts;
636
637 // get system default font spec
638 // as default default ;)
639 SibylFontToFontSpec( Screen.DefaultFont,
640 FDefaultFontSpec );
641
642end;
643
644// Destructor
645//------------------------------------------------------------------------
646destructor TCanvasFontManager.Destroy;
647var
648 i: integer;
649 Font: TLogicalFont;
650 rc: ERRORID;
651begin
652 // select default font so none of our logical fonts are in use
653 if not GpiSetCharSet( FCanvas.Handle, LCID_DEFAULT ) then
654 rc := WinGetLastError( AppHandle );
655
656 // delete each logical font and our record of it
657 for i := 0 to FLogicalFonts.Count - 1 do
658 begin
659 Font := FLogicalFonts[ i ];
660 if not GpiDeleteSetID( FCanvas.Handle, Font.ID ) then
661 rc := WinGetLastError( AppHandle );
662 Font.Destroy;
663 end;
664 FLogicalFonts.Destroy;
665 inherited Destroy;
666end;
667
668// Create a logical font for the given spec
669//------------------------------------------------------------------------
670function TCanvasFontManager.CreateFont( const FontSpec: TFontSpec ): TLogicalFont;
671var
672 UseFaceName: string;
673 Face: TFontFace;
674 RemoveBoldFromSelection: boolean;
675 RemoveItalicFromSelection: boolean;
676 UseAttributes: TFontAttributes;
677 MatchAttributes: TFontAttributes;
678 BaseFont: TLogicalFont;
679 BaseFontIsBitmapFont: Boolean;
680 FontInfo: FONTMETRICS;
681 FixedWidth: boolean;
682begin
683 Face := nil;
684 RemoveBoldFromSelection := false;
685 RemoveItalicFromSelection := false;
686
687 UseAttributes := FontSpec.Attributes;
688
689 // see if the originally specified font is a fixed width one.
690 FixedWidth := false;
691 Face := FindFaceName( FontSpec.FaceName );
692 if Face <> nil then
693 FixedWidth := Face.FixedWidth;
694
695 Face := nil;
696
697 if not FAllowBitmapFonts then
698 UseFaceName := SubstituteBitmapFontToOutline( FontSpec.FaceName )
699 else
700 UseFaceName := FontSpec.FaceName;
701
702 if FontSpec.Attributes <> [] then
703 begin
704 BaseFontIsBitmapFont := false;
705 if FAllowBitmapFonts then
706 begin
707 // First see if the base font (without attributes)
708 // would be a bitmap font...
709 BaseFont := CreateFontBasic( UseFaceName, FontSpec.PointSize );
710 if BaseFont <> nil then
711 begin
712 BaseFontIsBitmapFont := BaseFont.FontType = ftBitmap;
713 BaseFont.Destroy;
714 end;
715 end;
716
717 If not BaseFontIsBitmapFont Then
718 begin
719 // Result is an outline font so look for specific bold/italic fonts
720 if ( faBold in FontSpec.Attributes )
721 and ( faItalic in FontSpec.Attributes ) then
722 begin
723 Face := FindFaceName( UseFaceName + ' BOLD ITALIC' );
724 if Face <> nil then
725 begin
726 Exclude( UseAttributes, faBold );
727 Exclude( UseAttributes, faItalic );
728 RemoveBoldFromSelection := true;
729 RemoveItalicFromSelection := true;
730 end;
731 end;
732
733 if Face = nil then
734 if faBold in FontSpec.Attributes then
735 begin
736 Face := FindFaceName( UseFaceName + ' BOLD' );
737 if Face <> nil then
738 begin
739 Exclude( UseAttributes, faBold );
740 RemoveBoldFromSelection := true;
741 end;
742 end;
743
744 if Face = nil then
745 if faItalic in FontSpec.Attributes then
746 begin
747 Face := FindFaceName( UseFaceName + ' ITALIC' );
748 if Face <> nil then
749 begin
750 Exclude( UseAttributes, faItalic );
751 RemoveItalicFromSelection := true;
752 end;
753 end;
754 end;
755 end;
756
757 if Face <> nil then
758 // found a styled face, does it match fixed width?
759 if Face.FixedWidth <> FixedWidth then
760 // no so we don't want to use it.
761 Face := nil;
762
763 if Face = nil then
764 // didn't find a styled face (or no styles set)
765 // so find unmodified, we will use simulation bits
766 Face := FindFaceName( UseFaceName );
767
768 if not FAllowBitmapFonts then
769 if Face.FontType = ftBitmap then
770 // we aren't allowed bitmaps, but that's what this
771 // face is. So use the default outline face of the
772 // appropriate width type
773 if FixedWidth then
774 Face := DefaultOutlineFixedFace
775 else
776 Face := DefaultOutlineProportionalFace;
777
778 if Face = nil then
779 begin
780 // Could not find the specified font name. Bummer.
781 Result := nil;
782 exit;
783 end;
784
785 // OK now we have found the font face...
786
787 Result := TLogicalFont.Create( nil );
788
789 Result.PointSize := FontSpec.PointSize; // will use later if the result was an outline font...
790 Result.pFaceName := NewStr( FontSpec.FaceName );
791 Result.pUseFaceName := NewStr( Face.pName^ );
792 Result.Attributes := FontSpec.Attributes;
793
794 Result.fsSelection := 0;
795
796 Result.FixedWidth := Face.FixedWidth;
797
798 if FAllowBitmapFonts then
799 begin
800 if BaseFontIsBitmapFont then
801 MatchAttributes := []
802 else
803 MatchAttributes := UseAttributes;
804 FindBestFontMatch( Face.pName^,
805 FontSpec.PointSize,
806 MatchAttributes,
807 FixedWidth,
808 FontInfo );
809
810 AssignStr( Result.pUseFaceName, FontInfo.szFaceName );
811
812 // We may actually get a bitmap OR an outline font back
813 If ( FontInfo.fsDefn And FM_DEFN_OUTLINE ) <> 0 Then
814 Result.FontType := ftOutline
815 else
816 Result.FontType := ftBitmap;
817 end
818 else
819 begin
820 // no bitmap fonts please.
821 Result.FontType := ftOutline
822 end;
823
824 // store the baseline and average char width.
825 // For bitmap fonts, these tell GPI which font we really want
826 // For outline fonts, we are just storing them for later ref.
827 Result.lMaxbaseLineExt := FontInfo.lMaxbaselineExt;
828 Result.lAveCharWidth := FontInfo.lAveCharWidth;
829 Result.lMaxCharInc := FontInfo.lMaxCharInc;
[418]830 Result.lEmInc := FontInfo.lEmInc;
[15]831
832 // Set style flags
833 with Result do
834 begin
835 If faBold in UseAttributes Then
836 fsSelection := fsSelection or FM_SEL_BOLD;
837 If faItalic in UseAttributes Then
838 fsSelection := fsSelection or FM_SEL_ITALIC;
839 If faUnderScore in UseAttributes Then
840 fsSelection := fsSelection or FM_SEl_UNDERSCORE;
841 If faStrikeOut in UseAttributes Then
842 fsSelection := fsSelection or FM_SEl_STRIKEOUT;
843 If faOutline in UseAttributes Then
844 fsSelection := fsSelection or FM_SEl_OUTlINE;
845 end;
846
847 Result.pCharWidthArray := Nil;
848end;
849
850// Register the given logical font with GPI and store for later use
851//------------------------------------------------------------------------
852procedure TCanvasFontManager.RegisterFont( Font: TLogicalFont );
853var
854 fa: FATTRS;
855 rc: LONG;
856begin
857 FLogicalFonts.Add( Font );
858 Font.ID := FLogicalFonts.Count + 1; // add 1 to stay out of Sibyl's way
859
860 // Initialise GPI font attributes
861 FillChar( fa, SizeOf( FATTRS ), 0 );
862 fa.usRecordLength := SizeOf( FATTRS );
863
864 // Copy facename and 'simulation' attributes from what we obtained
865 // earlier
866 fa.szFaceName := Font.pUseFaceName^;
867 fa.fsSelection := Font.fsSelection;
868
869 fa.lMatch := 0; // please Mr GPI be helpful and do clever stuff for us, we are ignorant
870
871 fa.idRegistry := 0; // IBM magic number
872 fa.usCodePage := 0; // use current codepage
873
874 If Font.FontType = ftOutline then
875 // Outline font wanted
876 fa.fsFontUse := FATTR_FONTUSE_OUTLINE Or FATTR_FONTUSE_TRANSFORMABLE
877 else
878 // bitmap font
879 fa.fsFontUse := 0;
880
881 // don't need mixing with graphics (for now)
882 fa.fsFontUse := fa.fsFontUse or FATTR_FONTUSE_NOMIX;
883
884 // copy char cell width/height from the (valid) one we
885 // found earlier in GetFont (will be zero for outline)
886 fa.lMaxbaseLineExt := Font.lMaxbaselineExt;
887 fa.lAveCharWidth := Font.lAveCharWidth;
888
889 fa.fsType := 0;
890
891 // create logical font
892 rc := GpiCreateLogFont( FCanvas.Handle,
893 nil,
894 Font.ID,
895 fa );
896end;
897
898// Select the given (existing) logical font
899//------------------------------------------------------------------------
900procedure TCanvasFontManager.SelectFont( Font: TLogicalFont;
901 Scale: longint );
902var
903 aHDC: HDC;
904 xRes: LongInt;
905 yRes: LongInt;
906 aSizeF: SIZEF;
907begin
908 // Select the logical font
909 GpiSetCharSet( FCanvas.Handle, Font.ID );
910 if Font.FontType = ftOutline then
911 begin
912 // For outline fonts, also set character Box
913 aHDC := GpiQueryDevice( FCanvas.Handle );
914 DevQueryCaps( aHDC,
915 CAPS_HORIZONTAL_FONT_RES,
916 1,
917 xRes );
918 DevQueryCaps( aHDC,
919 CAPS_VERTICAL_FONT_RES,
920 1,
921 yRes );
922
923 aSizeF.CX := 65536 * xRes* Font.PointSize Div 72 * Scale;
924 aSizeF.CY := 65536 * yRes* Font.PointSize Div 72 * Scale;
925
926 GpiSetCharBox( FCanvas.Handle, aSizeF );
927 end;
928end;
929
930// Get a font to match the given spec, creating or re-using an
931// existing font as needed.
932//------------------------------------------------------------------------
933function TCanvasFontManager.GetFont( const FontSpec: TFontSpec ): TLogicalFont;
934var
935 AFont: TLogicalFont;
936 FontIndex: integer;
937begin
938 for FontIndex := 0 to FLogicalFonts.Count - 1 do
939 begin
940 AFont := FLogicalFonts[ FontIndex ];
941 if AFont.PointSize = FontSpec.PointSize then
942 begin
943 if ( AFont.PointSize > 0 )
944 or ( ( AFont.lAveCharWidth = FontSpec.XSize )
945 and ( AFont.lMaxbaselineExt = FontSpec.YSize ) ) then
946 begin
947 if AFont.Attributes = FontSpec.Attributes then
948 begin
949 // search name last since it's the slowest thing
950 if AFont.pFaceName^ = FontSpec.FaceName then
951 begin
952 // Found a logical font already created
953 Result := AFont;
954 // done
955 exit;
956 end;
957 end;
958 end;
959 end;
960 end;
961
962 // Need to create new logical font
963 Result := CreateFont( FontSpec );
964 if Result <> nil then
965 begin
966 RegisterFont( Result );
967 end;
968end;
969
970// Set the current font for the canvas to match the given
971// spec, creating or re-using fonts as needed.
972//------------------------------------------------------------------------
973procedure TCanvasFontManager.SetFont( const FontSpec: TFontSpec );
974var
975 Font: TLogicalFont;
976 DefaultFontSpec: TFontSpec;
977begin
978 if FCurrentFontSpec = FontSpec then
979 // same font
980 exit;
981
982 Font := GetFont( FontSpec );
983
984 if Font = nil then
985 begin
986 // ack! Pfffbt! Couldn't find the font.
987
988 // Try to get the default font
989 Font := GetFont( FDefaultFontSpec );
990 if Font = nil then
991 begin
992 SibylFontToFontSpec( Screen.DefaultFont,
993 DefaultFontSpec );
994 Font := GetFont( DefaultFontSpec );
995 if Font = nil then
996 // Jimminy! We can't even get the default system font
997 raise Exception.Create( 'Could not access default font '
998 + 'in place of '
999 + FontSpec.FaceName
1000 + ' '
1001 + IntToStr( FontSpec.PointSize ) );
1002 end;
1003
1004 end;
1005
1006 SelectFont( Font, 1 );
1007 FCurrentFontSpec := FontSpec;
1008 FCurrentFont := Font;
1009end;
1010
1011// Get the widths of all characters for current font
1012// and other dimensions
1013//------------------------------------------------------------------------
1014procedure TCanvasFontManager.LoadMetrics;
1015var
1016 TheChar: Char;
1017 fm: FONTMETRICS;
1018begin
1019 // Retrieve all character widths
1020 if FCurrentFont.FontType = ftOutline then
1021 begin
1022 SelectFont( FCurrentFont, FontWidthPrecisionFactor );
1023 end;
1024
1025 // allocate memory for storing the char widths
1026 GetMem( FCurrentFont.pCharWidthArray,
1027 sizeof( TCharWidthArray ) );
1028 if not GpiQueryWidthTable( FCanvas.Handle,
1029 0, 256,
1030 FCurrentFont.pCharWidthArray^[ #0 ] ) then
1031 begin
1032 raise Exception.Create( 'Error getting character width table: '
1033 + 'GpiQueryWidthTable error '
1034 + IntToStr( WinGetLastError( AppHandle ) ) );
1035 end;
1036
1037 // Convert all widths to positive!
1038 // For unknown reason, sometimes GPI returns negative values...
1039 for TheChar := #0 to #255 do
1040 begin
1041 FCurrentFont.pCharWidthArray^[ TheChar ] := Abs( FCurrentFont.pCharWidthArray^[ TheChar ] );
1042 end;
1043
1044 if FCurrentFont.FontType = ftOutline then
1045 begin
1046 SelectFont( FCurrentFont, 1 );
1047 end
1048 else
1049 begin
1050 // For bitmap fonts, multiply by 256 manually
1051 for TheChar := #0 to #255 do
1052 begin
1053 FCurrentFont.pCharWidthArray^[ TheChar ] :=
1054 FCurrentFont.pCharWidthArray^[ TheChar ]
1055 * FontWidthPrecisionFactor;
1056 end;
1057 end;
1058
1059 GpiQueryFontMetrics( FCanvas.Handle,
1060 sizeof( fm ),
1061 fm );
1062 FCurrentFont.lMaxbaseLineExt := fm.lMaxbaselineExt;
1063 FCurrentFont.lAveCharWidth := fm.lAveCharWidth;
1064 FCurrentFont.lMaxCharInc := fm.lMaxCharInc;
1065 FCurrentFont.lMaxDescender := fm.lMaxDescender;
[418]1066 FCurrentFont.lEmInc := fm.lEmInc;
[15]1067end;
1068
1069procedure TCanvasFontManager.EnsureMetricsLoaded;
1070begin
1071 if FCurrentFont = nil then
1072 raise( Exception.Create( 'No font selected before getting font metrics' ) );
1073
1074 if FCurrentFont.pCharWidthArray = Nil then
1075 LoadMetrics;
1076end;
1077
1078function TCanvasFontManager.CharWidth( const C: Char ): longint;
1079begin
1080 EnsureMetricsLoaded;
1081 Result := FCurrentFont.pCharWidthArray^[ C ];
1082end;
1083
1084function TCanvasFontManager.AverageCharWidth: longint;
1085begin
1086 EnsureMetricsLoaded;
1087 Result := FCurrentFont.lAveCharWidth;
1088end;
1089
1090function TCanvasFontManager.MaximumCharWidth: longint;
1091begin
1092 EnsureMetricsLoaded;
1093 Result := FCurrentFont.lMaxCharInc;
1094end;
1095
1096function TCanvasFontManager.CharHeight;
1097begin
1098 EnsureMetricsLoaded;
1099 Result := FCurrentFont.lMaxBaseLineExt;
1100end;
1101
1102function TCanvasFontManager.CharDescender;
1103begin
1104 EnsureMetricsLoaded;
1105 Result := FCurrentFont.lMaxDescender;
1106end;
1107
1108function TCanvasFontManager.IsFixed: boolean;
1109begin
1110 Result := FCurrentFont.FixedWidth;
1111end;
1112
[418]1113// ALT begins
1114//
1115// A 'default' average width for DBCS characters; probably not very accurate.
1116// Should only be used as a fallback in case querying the actual string width
1117// is impossible/fails.
1118//
1119function TCanvasFontManager.CJKCharWidth: longint;
1120begin
1121 EnsureMetricsLoaded;
1122 if FCurrentFont.lMaxCharInc < FCurrentFont.lEmInc then
1123 Result := FCurrentFont.lMaxCharInc * FontWidthPrecisionFactor
1124 else
1125 Result := FCurrentFont.lEmInc * FontWidthPrecisionFactor;
1126end;
1127
1128// Get the render width of a CJK (Chinese/Japanese/Korean) character string.
1129//
1130function TCanvasFontManager.CJKTextWidth( const Length: longint; const S: PChar ): longint;
1131var
1132 aptl: Array[ 0..TXTBOX_COUNT-1 ] Of PointL;
1133begin
1134 EnsureMetricsLoaded;
1135 GpiQueryTextBox( FCanvas.Handle, Length, S^, TXTBOX_COUNT, aptl[0] );
[420]1136 Result := aptl[ TXTBOX_CONCAT ].x * FontWidthPrecisionFactor;
[418]1137end;
1138//
1139// ALT ends
1140
[15]1141procedure TCanvasFontManager.DrawString( Var Point: TPoint;
1142 const Length: longint;
1143 const S: PChar );
1144begin
1145 GpiCharStringAt( FCanvas.Handle,
1146 Point,
1147 Length,
1148 S^ );
1149 Point := FCanvas.PenPos;
1150end;
1151
1152end.
Note: See TracBrowser for help on using the repository browser.