1
unit LazFreeTypeFontCollection;
8
Classes, SysUtils, EasyLazFreeType, AvgLvlTree, LazFreeType, TTTypes;
11
{ TFontCollectionItem }
13
TFontCollectionItem = class(TCustomFontCollectionItem)
16
FInformation: array[TFreeTypeInformation] of string;
17
FVersionNumber: string;
18
FStyleList: array of string;
21
FUsePostscriptStyle: boolean;
22
procedure UpdateStyles;
23
procedure SetInformation(AIndex: TFreeTypeInformation; AValue: string);
24
procedure SetUsePostscriptStyle(AValue: boolean);
26
function GetFilename: string; override;
27
function GetBold: boolean; override;
28
function GetInformation(AIndex: TFreeTypeInformation): string; override;
29
function GetItalic: boolean; override;
30
function GetStyleCount: integer; override;
31
function GetStyles: string; override;
32
function GetStyle(AIndex: integer): string; override;
33
function GetVersionNumber: string; override;
35
constructor Create(AFilename: string);
36
destructor Destroy; override;
37
function HasStyle(AStyle: string): boolean; override;
38
property Information[AIndex: TFreeTypeInformation]: string read GetInformation write SetInformation;
39
property VersionNumber: string read GetVersionNumber write FVersionNumber;
40
function CreateFont: TFreeTypeFont; override;
41
function QueryFace: TT_Face; override;
42
procedure ReleaseFace; override;
43
property UsePostscriptStyle: boolean read FUsePostscriptStyle write SetUsePostscriptStyle;
46
{ TFamilyCollectionItem }
48
TFamilyCollectionItem = class(TCustomFamilyCollectionItem)
51
FFonts: array of TFontCollectionItem;
53
FStyles: array of string;
55
FUsePostscriptStyle: boolean;
57
function GetFontByIndex(AIndex: integer): TCustomFontCollectionItem; override;
58
function GetFontByStyles(AStyles: string): TCustomFontCollectionItem;
59
function GetFontIndexByStyles(AStyles: string): integer;
60
function GetStyle(AIndex: integer): string; override;
61
procedure AddStyle(AName: string);
62
function GetStyles: string; override;
63
function GetFamilyName: string; override;
64
function GetFontCount: integer; override;
65
function GetStyleCount: integer; override;
67
constructor Create(AName: string);
68
procedure AddFont(AFontItem: TFontCollectionItem);
69
function GetFont(const AStyles: array of string; NeedAllStyles: boolean = false; NoMoreStyle: boolean = false): TCustomFontCollectionItem; override;
70
function GetFont(AStyle: string; NeedAllStyles: boolean = false; NoMoreStyle: boolean = false): TCustomFontCollectionItem; override;
71
function GetFontIndex(const AStyles: array of string; NeedAllStyles: boolean = false; NoMoreStyle: boolean = false): integer; override;
72
function GetFontIndex(AStyle: string; NeedAllStyles: boolean = false; NoMoreStyle: boolean = false): integer; override;
73
function HasStyle(AName: string): boolean; override;
76
{ TFreeTypeFontCollection }
78
TFreeTypeFontCollection = class(TCustomFreeTypeFontCollection)
80
FFontList: TAvgLvlTree;
81
FTempFont: TFreeTypeFont;
82
FUpdateCount: integer;
84
FFamilyList: TAvgLvlTree;
86
function AddFamily(AName: string): TFamilyCollectionItem;
87
function FindFamily(AName: string): TFamilyCollectionItem;
88
function FindFont(AFileName: string): TFontCollectionItem;
90
function CompareFontFileName({%H-} Tree: TAvgLvlTree; Data1, Data2: Pointer): integer;
91
function CompareFamilyName({%H-} Tree: TAvgLvlTree; Data1, Data2: Pointer): integer;
94
function GetFont(AFileName: string): TCustomFontCollectionItem; override;
95
function GetFamily(AName: string): TCustomFamilyCollectionItem; override;
96
function GetFamilyCount: integer; override;
97
function GetFontCount: integer; override;
100
constructor Create; override;
101
procedure Clear; override;
102
procedure BeginUpdate; override;
103
procedure AddFolder(AFolder: string); override;
104
function AddFile(AFilename: string): boolean; override;
105
procedure EndUpdate; override;
106
destructor Destroy; override;
107
function FontFileEnumerator: IFreeTypeFontEnumerator; override;
108
function FamilyEnumerator: IFreeTypeFamilyEnumerator; override;
114
{ TFamilyEnumerator }
116
TFamilyEnumerator = class(TInterfacedObject,IFreeTypeFamilyEnumerator)
118
FNodeEnumerator: TAvgLvlTreeNodeEnumerator;
120
constructor Create(ANodeEnumerator: TAvgLvlTreeNodeEnumerator);
121
destructor Destroy; override;
122
function MoveNext: boolean;
123
function GetCurrent: TCustomFamilyCollectionItem;
128
TFontEnumerator = class(TInterfacedObject,IFreeTypeFontEnumerator)
130
FNodeEnumerator: TAvgLvlTreeNodeEnumerator;
132
constructor Create(ANodeEnumerator: TAvgLvlTreeNodeEnumerator);
133
destructor Destroy; override;
134
function MoveNext: boolean;
135
function GetCurrent: TCustomFontCollectionItem;
138
{ TFontCollectionItem }
140
function TFontCollectionItem.GetStyles: string;
143
if StyleCount = 0 then
148
for i := 0 to StyleCount-1 do
150
if i > 0 then result += ' ';
156
function TFontCollectionItem.GetInformation(AIndex: TFreeTypeInformation): string;
158
if (AIndex < low(TFreeTypeInformation)) or (AIndex > high(TFreeTypeInformation)) then
161
result := FInformation[AIndex];
164
function TFontCollectionItem.GetBold: boolean;
166
result := HasStyle('Bold');
169
function TFontCollectionItem.GetItalic: boolean;
171
result := HasStyle('Italic') or HasStyle('Oblique');
174
function TFontCollectionItem.GetStyleCount: integer;
176
result := length(FStyleList);
179
procedure TFontCollectionItem.SetInformation(AIndex: TFreeTypeInformation;
182
if (AIndex >= low(TFreeTypeInformation)) and (AIndex <= high(TFreeTypeInformation)) then
184
FInformation[AIndex] := AValue;
185
if ((AIndex = ftiStyle) and not FUsePostscriptStyle) or
186
((AIndex = ftiPostscriptName) and FUsePostscriptStyle) then UpdateStyles;
190
procedure TFontCollectionItem.SetUsePostscriptStyle(AValue: boolean);
192
if AValue <> FUsePostscriptStyle then
194
FUsePostscriptStyle:= AValue;
199
function TFontCollectionItem.GetFilename: string;
204
function TFontCollectionItem.GetStyle(AIndex: integer): string;
206
if (AIndex < 0) or (AIndex > high(FStyleList)) then
209
result := FStyleList[AIndex];
212
function TFontCollectionItem.GetVersionNumber: string;
214
result := FVersionNumber;
217
procedure TFontCollectionItem.UpdateStyles;
222
if not FUsePostscriptStyle then
223
StyleStr := Information[ftiStyle]
226
StyleStr := Information[ftiPostscriptName];
227
idx := pos('-',StyleStr);
228
if idx = 0 then StyleStr := 'Regular' else
230
StyleStr := copy(StyleStr,idx+1,length(StyleStr)-idx);
231
for i := length(StyleStr) downto 2 do
232
if (StyleStr[i] = UpCase(StyleStr[i])) and
233
(StyleStr[i-1] <> UpCase(StyleStr[i-1])) then
234
Insert(' ',StyleStr,i);
235
if (length(StyleStr) > 2) and (copy(StyleStr, length(StyleStr)-2,3)=' MT') then
236
delete(StyleStr, length(StyleStr)-2,3);
239
FStyleList := StylesToArray(StyleStr);
242
constructor TFontCollectionItem.Create(AFilename: string);
244
FFilename:= AFilename;
247
FUsePostscriptStyle:= false;
250
destructor TFontCollectionItem.Destroy;
252
if FFaceUsage <> 0 then
254
TT_Close_Face(FFace);
260
function TFontCollectionItem.HasStyle(AStyle: string): boolean;
263
if CompareText(AStyle,'Regular')=0 then
265
result := length(FStyleList)=0;
268
for i := 0 to high(FStyleList) do
269
if CompareText(FStyleList[i],AStyle)=0 then
277
function TFontCollectionItem.CreateFont: TFreeTypeFont;
279
result := TFreeTypeFont.Create;
280
result.Name := Filename;
283
function TFontCollectionItem.QueryFace: TT_Face;
284
var errorNum: TT_Error;
286
if FFaceUsage = 0 then
288
errorNum := TT_Open_Face(Filename,FFace);
289
if errorNum <> TT_Err_Ok then
290
raise exception.Create('Cannot open font (TT_Error ' + intToStr(errorNum)+')');
296
procedure TFontCollectionItem.ReleaseFace;
298
if FFaceUsage > 0 then
301
if FFaceUsage = 0 then TT_Close_Face(FFace);
305
constructor TFontEnumerator.Create(ANodeEnumerator: TAvgLvlTreeNodeEnumerator);
307
FNodeEnumerator := ANodeEnumerator;
310
destructor TFontEnumerator.Destroy;
312
FNodeEnumerator.Free;
315
function TFontEnumerator.MoveNext: boolean;
317
result := FNodeEnumerator.MoveNext;
320
function TFontEnumerator.GetCurrent: TCustomFontCollectionItem;
322
result := TCustomFontCollectionItem(FNodeEnumerator.Current.Data);
325
{ TFamilyEnumerator }
327
function TFamilyEnumerator.GetCurrent: TCustomFamilyCollectionItem;
329
result := TCustomFamilyCollectionItem(FNodeEnumerator.Current.Data);
332
constructor TFamilyEnumerator.Create(ANodeEnumerator: TAvgLvlTreeNodeEnumerator );
334
FNodeEnumerator := ANodeEnumerator;
337
destructor TFamilyEnumerator.Destroy;
339
FNodeEnumerator.Free;
342
function TFamilyEnumerator.MoveNext: boolean;
344
result := FNodeEnumerator.MoveNext;
347
{ TFamilyCollectionItem }
349
function TFamilyCollectionItem.GetFontByIndex(AIndex: integer): TCustomFontCollectionItem;
352
result := GetFont('Regular')
354
if (AIndex < 0) or (AIndex >= FFontCount) then
357
result := FFonts[AIndex];
360
function TFamilyCollectionItem.GetFontByStyles(AStyles: string): TCustomFontCollectionItem;
363
for i := 0 to FFontCount-1 do
364
if CompareText(FFonts[i].Styles,AStyles)= 0 then
372
function TFamilyCollectionItem.GetFontIndexByStyles(AStyles: string): integer;
375
for i := 0 to FFontCount-1 do
376
if CompareText(FFonts[i].Styles,AStyles)= 0 then
384
function TFamilyCollectionItem.GetStyle(AIndex: integer): string;
386
if (AIndex < 0) or (AIndex >= FStyleCount) then
389
result := FStyles[AIndex];
392
procedure TFamilyCollectionItem.AddStyle(AName: string);
394
if HasStyle(AName) then exit;
395
if FStyleCount = length(FStyles) then
396
setlength(FStyles, length(FStyles)+4);
397
FStyles[FStyleCount] := AName;
401
function TFamilyCollectionItem.GetStyles: string;
405
for i := 0 to StyleCount-1 do
407
if i <> 0 then result += ' ';
412
function TFamilyCollectionItem.GetFamilyName: string;
414
result := FFamilyName;
417
function TFamilyCollectionItem.GetFontCount: integer;
419
result := FFontCount;
422
function TFamilyCollectionItem.GetStyleCount: integer;
424
result := FStyleCount;
427
constructor TFamilyCollectionItem.Create(AName: string);
434
FUsePostscriptStyle:= false;
437
procedure TFamilyCollectionItem.AddFont(AFontItem: TFontCollectionItem);
439
DuplicateStyle: boolean;
440
StyleNumber: integer;
441
TempStyles,BaseStyle: string;
443
if FFontCount = length(FFonts) then
444
setlength(FFonts, length(FFonts)+4);
446
FFonts[FFontCount] := AFontItem;
449
if FUsePostscriptStyle then AFontItem.UsePostscriptStyle := true;
451
for i := 0 to AFontItem.StyleCount -1 do
452
AddStyle(AFontItem.Style[i]);
454
DuplicateStyle := false;
455
for i := 0 to FFontCount-2 do
456
if FFonts[i].Styles = AFontItem.Styles then
458
DuplicateStyle:= true;
462
if DuplicateStyle and not FUsePostscriptStyle then
463
begin //try with postscript styles instead
464
FUsePostscriptStyle:= true;
466
DuplicateStyle := false;
467
for i := 0 to FFontCount-1 do
469
FFonts[i].UsePostscriptStyle := true;
470
for j := 0 to FFonts[i].StyleCount -1 do
471
AddStyle(FFonts[i].Style[j]);
474
if FFonts[j].Styles = FFonts[i].Styles then
476
DuplicateStyle:= true;
482
if DuplicateStyle then
485
BaseStyle := AFontItem.Styles;
486
if BaseStyle = 'Regular' then BaseStyle := 'Unknown';
488
if StyleNumber = 1 then
489
TempStyles := BaseStyle
491
TempStyles := BaseStyle+' '+IntToStr(StyleNumber);
492
DuplicateStyle := false;
493
for i := 0 to FFontCount-2 do
494
if FFonts[i].Styles = TempStyles then
496
DuplicateStyle:= true;
499
until not DuplicateStyle;
500
AFontItem.Information[ftiStyle] := TempStyles;
503
if AFontItem.StyleCount = 0 then AddStyle('Regular');
506
function TFamilyCollectionItem.GetFont(const AStyles: array of string;
507
NeedAllStyles: boolean; NoMoreStyle: boolean): TCustomFontCollectionItem;
510
idx := GetFontIndex(AStyles,NeedAllStyles,NoMoreStyle);
511
if idx = -1 then result := nil
512
else result := Font[idx];
515
function TFamilyCollectionItem.GetFontIndex(const AStyles: array of string; NeedAllStyles: boolean; NoMoreStyle: boolean): integer;
516
var curCount,curMissing,maxStyleCount,minMissingCount: integer;
521
minMissingCount := 0;
523
for i := 0 to FontCount-1 do
527
for j := 0 to high(AStyles) do
528
if Font[i].HasStyle(AStyles[j]) then
530
curMissing := Font[i].StyleCount-curCount;
531
if NeedAllStyles and (curCount <> length(AStyles)) then continue;
532
if NoMoreStyle and (curMissing > 0) then continue;
533
if (curCount > maxStyleCount) or ((curCount = maxStyleCount) and (curMissing < minMissingCount)) then
535
maxStyleCount := curCount;
536
minMissingCount:= curMissing;
541
for i := 0 to FontCount-1 do
545
for j := 0 to high(AStyles) do
546
if Font[i].HasStyle(AStyles[j]) or
547
((CompareText(AStyles[j],'Italic')=0) and Font[i].HasStyle('Oblique')) or
548
((CompareText(AStyles[j],'Oblique')=0) and Font[i].HasStyle('Italic')) then
550
curMissing := Font[i].StyleCount-curCount;
551
if NeedAllStyles and (curCount <> length(AStyles)) then continue;
552
if NoMoreStyle and (curMissing > 0) then continue;
553
if (curCount > maxStyleCount) or ((curCount = maxStyleCount) and (curMissing < minMissingCount)) then
555
maxStyleCount := curCount;
556
minMissingCount:= curMissing;
563
function TFamilyCollectionItem.GetFontIndex(AStyle: string;
564
NeedAllStyles: boolean; NoMoreStyle: boolean): integer;
566
result := GetFontIndexByStyles(AStyle); //exact match
568
result := GetFontIndex(StylesToArray(AStyle),NeedAllStyles,NoMoreStyle);
571
function TFamilyCollectionItem.GetFont(AStyle: string; NeedAllStyles: boolean; NoMoreStyle: boolean): TCustomFontCollectionItem;
573
result := GetFontByStyles(AStyle); //exact match
575
result := GetFont(StylesToArray(AStyle),NeedAllStyles,NoMoreStyle);
578
function TFamilyCollectionItem.HasStyle(AName: string): boolean;
581
for i := 0 to FStyleCount-1 do
582
if CompareText(FStyles[i],AName)=0 then
592
function TFreeTypeFontCollection.GetFontCount: integer;
594
result := FFontList.Count;
597
function TFreeTypeFontCollection.GetFamilyCount: integer;
599
result := FFamilyList.Count;
602
function TFreeTypeFontCollection.FindFont(AFileName: string): TFontCollectionItem;
604
node : TAvgLvlTreeNode;
606
node:= FFontList.Root;
607
while (node<>nil) do begin
608
Comp:=CompareStr(AFileName,TFontCollectionItem(node.Data).Filename);
609
if Comp=0 then break;
619
result := TFontCollectionItem(node.Data);
622
function TFreeTypeFontCollection.GetFamily(AName: string
623
): TCustomFamilyCollectionItem;
627
result := GetFamily('Arial');
630
result := FindFamily(AName);
631
if (result = nil) and (CompareText(AName,'Arial')=0) then result := FindFamily('Helvetica');
632
if (result = nil) and (CompareText(AName,'Helvetica')=0) then result := FindFamily('Arial');
633
if (result = nil) and (CompareText(AName,'Courier New')=0) then result := FindFamily('Nimbus Monospace');
634
if (result = nil) and (CompareText(AName,'Courier New')=0) then result := FindFamily('Courier');
635
if (result = nil) and (CompareText(AName,'Nimbus Monospace')=0) then result := FindFamily('Courier New');
636
if (result = nil) and (CompareText(AName,'Nimbus Monospace')=0) then result := FindFamily('Courier');
637
if (result = nil) and (CompareText(AName,'Courier')=0) then result := FindFamily('Courier New');
638
if (result = nil) and (CompareText(AName,'Courier')=0) then result := FindFamily('Nimbus Monospace');
639
if (result = nil) and (CompareText(AName,'Times')=0) then result := FindFamily('Times New Roman');
640
if (result = nil) and (CompareText(AName,'Times')=0) then result := FindFamily('CG Times');
641
if (result = nil) and (CompareText(AName,'Times New Roman')=0) then result := FindFamily('Times');
642
if (result = nil) and (CompareText(AName,'Times New Roman')=0) then result := FindFamily('CG Times');
643
if (result = nil) and (CompareText(AName,'CG Times')=0) then result := FindFamily('Times');
644
if (result = nil) and (CompareText(AName,'CG Times')=0) then result := FindFamily('Times New Roman');
647
function TFreeTypeFontCollection.AddFamily(AName: string): TFamilyCollectionItem;
649
f: TFamilyCollectionItem;
651
f := FindFamily(AName);
654
result := TFamilyCollectionItem.Create(AName);
655
FFamilyList.Add(result);
660
function TFreeTypeFontCollection.FindFamily(AName: string): TFamilyCollectionItem;
662
node : TAvgLvlTreeNode;
664
node:= FFamilyList.Root;
665
while (node<>nil) do begin
666
Comp:=CompareText(AName,TFamilyCollectionItem(node.Data).FamilyName);
667
if Comp=0 then break;
677
result := TFamilyCollectionItem(node.Data);
680
function TFreeTypeFontCollection.CompareFontFileName(Tree: TAvgLvlTree; Data1,
681
Data2: Pointer): integer;
683
result := CompareStr(TFontCollectionItem(Data1).Filename,TFontCollectionItem(Data2).Filename);
686
function TFreeTypeFontCollection.CompareFamilyName(Tree: TAvgLvlTree; Data1,
687
Data2: Pointer): integer;
689
result := CompareText(TFamilyCollectionItem(Data1).FamilyName,TFamilyCollectionItem(Data2).FamilyName);
692
function TFreeTypeFontCollection.GetFont(AFileName: string
693
): TCustomFontCollectionItem;
695
result := FindFont(AFilename);
698
constructor TFreeTypeFontCollection.Create;
702
FFontList := TAvgLvlTree.CreateObjectCompare(@CompareFontFileName);
703
FFamilyList := TAvgLvlTree.CreateObjectCompare(@CompareFamilyName);
706
procedure TFreeTypeFontCollection.Clear;
708
FFamilyList.FreeAndClear;
709
FFontList.FreeAndClear;
712
procedure TFreeTypeFontCollection.BeginUpdate;
714
if (FUpdateCount = 0) and (FTempFont = nil) then
715
FTempFont := TFreeTypeFont.Create;
719
procedure TFreeTypeFontCollection.AddFolder(AFolder: string);
724
if (length(AFolder) <> 0) and (AFolder[length(AFolder)] <> PathDelim) then
725
AFolder += PathDelim;
727
files := TStringList.Create;
730
if FindFirst(AFolder+'*.ttf',faAnyfile,sr) = 0 then
732
if sr.Attr and (faDirectory+faVolumeId) = 0 then
733
files.Add(AFolder+sr.Name);
734
until FindNext(sr) <> 0;
737
for i := 0 to files.Count-1 do
745
function TFreeTypeFontCollection.AddFile(AFilename: string): boolean;
746
var info: TFreeTypeInformation;
748
item: TFontCollectionItem;
749
f: TFamilyCollectionItem;
754
FTempFont.Name := AFilename;
755
fName := FTempFont.Family;
758
f := AddFamily(fName);
759
item := TFontCollectionItem.Create(AFilename);
763
VersionNumber:= FTempFont.VersionNumber;
764
for info := low(TFreeTypeInformation) to high(TFreeTypeInformation) do
765
Information[info] := FTempFont.Information[info];
775
procedure TFreeTypeFontCollection.EndUpdate;
777
if FUpdateCount > 0 then
780
if FUpdateCount = 0 then FreeAndNil(FTempFont);
784
destructor TFreeTypeFontCollection.Destroy;
793
function TFreeTypeFontCollection.FontFileEnumerator: IFreeTypeFontEnumerator;
795
result := TFontEnumerator.Create(FFontList.GetEnumerator);
798
function TFreeTypeFontCollection.FamilyEnumerator: IFreeTypeFamilyEnumerator;
800
result := TFamilyEnumerator.Create(FFamilyList.GetEnumerator);
804
InternalDefaultFontCollection : TFreeTypeFontCollection;
808
InternalDefaultFontCollection := TFreeTypeFontCollection.Create;
809
FontCollection := InternalDefaultFontCollection;
813
InternalDefaultFontCollection.Free;