spiegel-keyman/common/windows/delphi/general/findfonts.pas

443 lines
13 KiB
ObjectPascal

(*
Name: findfonts
Copyright: Copyright (C) SIL International.
Documentation:
Description:
Create Date: 27 Mar 2008
Modified Date: 3 Aug 2015
Authors: mcdurdin
Related Files:
Dependencies:
Bugs:
Todo:
Notes:
History: 27 Mar 2008 - mcdurdin - Initial version
14 Jun 2008 - mcdurdin - I1484 - Fix fonts not correctly locating characters
29 Mar 2010 - mcdurdin - I2261 - Font list shows bold or italic instead of plain version
29 Mar 2010 - mcdurdin - I2267 - Font helper shows incorrect names for some fonts
24 Jun 2010 - mcdurdin - I2421 - Font helper lists matching characters
29 Jun 2010 - mcdurdin - I2421 - Test against blank and invalid glyphs as well as default
18 Feb 2011 - mcdurdin - I2715 - Percentage showed as wrong for some fonts with missing characters
18 May 2012 - mcdurdin - I3306 - V9.0 - Remove TntControls + Win9x support
03 Aug 2015 - mcdurdin - I4828 - Font identifier miscalculates percentages when supplementary plane characters in string
*)
unit findfonts; // I3306
interface
uses
Classes, Contnrs;
type
TFindFontResult = class
FontName: WideString;
Coverage: Integer; { 0 - 100, only fonts with 90% + }
IncludedChars: WideString;
ExcludedChars: WideString;
end;
TFindFontList = class(TObjectList)
private
function GetItem(Index: Integer): TFindFontResult;
procedure SetItem(Index: Integer; const Value: TFindFontResult);
public
function Add(FontName: WideString; Coverage: Integer; IncludedChars, ExcludedChars: WideString): Integer;
function IndexOfName(const Name: WideString): Integer;
property Items[Index: Integer]: TFindFontResult read GetItem write SetItem; default;
end;
procedure FindFontsForChars(const Chars: WideString; MinCoverage: Integer; Fonts: TFindFontList);
implementation
uses
unicode,
SysUtils,
StrUtils,
Windows;
const
USP_E_SCRIPT_NOT_IN_FONT: HRESULT = HRESULT($80040200);
type
PSCRIPT_CACHE = ^SCRIPT_CACHE;
SCRIPT_CACHE = Pointer;
SCRIPT_STATE = packed record
uBidiLevel_Etc_: WORD;
end;
SCRIPT_ANALYSIS = packed record
eScript_Etc: WORD;
s: SCRIPT_STATE;
end;
SCRIPT_ITEM = packed record
iCharPos: Integer;
a: SCRIPT_ANALYSIS;
end;
PSCRIPT_ITEM = ^SCRIPT_ITEM;
SCRIPT_VISATTR = packed record
uJustification_Etc_: WORD;
end;
PSCRIPT_VISATTR = ^SCRIPT_VISATTR;
SCRIPT_CONTROL = packed record
uDefaultLanguage_Etc_: DWORD;
end;
TFindFontEnumData = record
hdc: HDC;
Chars: WideString;
MinCoverage: Integer;
ExcludedFonts: TStringList;
Fonts: TFindFontList;
itemcount: Integer;
items: PSCRIPT_ITEM;
outglyphs: PWord;
end;
SCRIPT_FONTPROPERTIES = record
cBytes: integer;
wgBlank: WORD;
wgDefault: WORD;
wgInvalid: WORD;
wgKashida: WORD;
iKashidaWidth: Integer;
end;
PFindFontEnumData = ^TFindFontEnumData;
function ScriptItemize(text: PWCHAR; text_length: Integer; maxItems: Integer;
var control: SCRIPT_CONTROL; var state: SCRIPT_STATE; items: PSCRIPT_ITEM; var item_count: Integer): HRESULT; stdcall; external 'usp10.dll';
function ScriptShape(hdc: HDC; var psc: SCRIPT_CACHE; pwcChars: PWCHAR; cChars: Integer; cMaxGlyphs: Integer;
var psa: SCRIPT_ANALYSIS; OutGlyphs: PWORD; LogClust: PWORD; psva: PSCRIPT_VISATTR; var pcGlyphs: Integer): HRESULT; stdcall; external 'usp10.dll';
function ScriptGetCMap(hdc: HDC; var psc: SCRIPT_CACHE; wsz: PWideChar; cChars: Integer; dwFlags: DWORD; pwOutGlyphs: PWORD): HRESULT; stdcall; external 'usp10.dll';
function ScriptGetFontProperties(hdc: HDC; var psc: SCRIPT_CACHE; var sfp: SCRIPT_FONTPROPERTIES): HRESULT; stdcall; external 'usp10.dll';
function ScriptFreeCache(var psc: SCRIPT_CACHE): HRESULT; stdcall; external 'usp10.dll';
function DoScriptItemize(const text: WideString; var items: PSCRIPT_ITEM; var itemcount: Integer): Boolean;
var
control: SCRIPT_CONTROL;
state: SCRIPT_STATE;
max_items: Integer;
hr: HRESULT;
begin
FillChar(control, sizeof(control), 0);
FillChar(state, sizeof(state), 0);
items := nil;
max_items := 8;
repeat
max_items := max_items * 2;
ReallocMem(items, max_items * sizeof(SCRIPT_ITEM));
itemcount := 0;
hr := ScriptItemize(PWideChar(text), Length(text), max_items - 1, control, state, items, itemcount);
if SUCCEEDED(hr) then
begin
Inc(itemcount);
ReallocMem(items, itemcount * sizeof(SCRIPT_ITEM));
Result := True;
Exit;
end;
until hr <> E_OUTOFMEMORY;
FreeMem(items);
Result := False;
end;
function DoScriptShape(input: PWideChar; input_length: Integer;
hfont: THandle; var uscript_cache: SCRIPT_CACHE; temp_dc: HDC;
var analysis: SCRIPT_ANALYSIS;
var logs: PWORD;
var glyphs: PWORD;
var visattr: PSCRIPT_VISATTR;
var glyphs_used: Integer): HRESULT;
var
glyphs_size: Integer;
begin
logs := AllocMem(input_length * sizeof(WORD));
glyphs_size := input_length * 3 div 2 + 16;
glyphs := AllocMem(glyphs_size * sizeof(WORD));
visattr := AllocMem(glyphs_size * sizeof(WORD));
// Result := E_UNEXPECTED;
while True do
begin
Result := ScriptShape(temp_dc, uscript_cache, input, input_length, glyphs_size, analysis,
glyphs, logs, visattr, glyphs_used);
if SUCCEEDED(Result) then
begin
// It worked, resize the output list to the exact number it returned.
ReallocMem(glyphs, glyphs_used * sizeof(WORD));
Break;
end;
// Different types of failure...
if Result = E_OUTOFMEMORY then
begin
// The glyph buffer needs to be larger. Just double it every time.
glyphs_size := glyphs_size * 2;
ReallocMem(glyphs, glyphs_size * sizeof(WORD));
ReallocMem(visattr, glyphs_size * sizeof(WORD));
// Loop again...
end
else if Result = USP_E_SCRIPT_NOT_IN_FONT then
begin
// The font you selected doesn't have enough information to display
// what you want. You'll have to pick another one somehow...
// For our cases, we'll just return failure.
Break;
end
else
begin
// Some other failure.
Break;
end;
end;
if not Succeeded(Result) then
begin
FreeMem(visattr); visattr := nil;
FreeMem(logs); logs := nil;
FreeMem(glyphs); glyphs := nil;
end;
end;
function FindFontEnum(lpelfe: PENUMLOGFONTEX; lpntme: PNewTextMetricEx; FontType: DWORD; lParam: LPARAM): integer; stdcall;
var
data: PFindFontEnumData;
hfont: THandle;
analysis: SCRIPT_ANALYSIS;
props: SCRIPT_FONTPROPERTIES;
nTotal: Integer;
nDef: Integer;
I: Integer;
logs: PWord;
glyphs: PWord;
visattr: PSCRIPT_VISATTR;
glyphs_used: Integer;
temp_dc: THandle;
old_font: THandle;
item, item2: PSCRIPT_ITEM;
j: Integer;
hr: HRESULT;
psc: SCRIPT_CACHE;
nGlyph: Word;
IncludedChars: WideString;
ExcludedChars: WideString;
begin
Result := 1;
if FontType <> TRUETYPE_FONTTYPE then Exit;
data := PFindFontEnumData(lParam);
if data.Fonts.IndexOfName(lpelfe.elfLogFont.lfFaceName) >= 0 then Exit;
if data.ExcludedFonts.IndexOf(lpelfe.elfLogFont.lfFaceName) >= 0 then Exit;
if lpelfe.elfLogFont.lfFaceName[0] = '@' then
begin
data.ExcludedFonts.Add(lpelfe.elfLogFont.lfFaceName);
Exit; // Ignore Far-East font names
end;
SetLastError(0);
////if Copy(lpelfe.elfLogFont.lfFaceName, 1, Length('Palatino Linotype')) <> 'Palatino Linotype' then Exit;
hfont := CreateFontIndirect(lpelfe.elfLogFont);
if GetLastError <> 0 then RaiseLastOSError;
try
temp_dc := GetDC(0);
old_font := SelectObject(temp_dc, hfont);
try
nDef := 0;
nTotal := 0;
item := data.items;
item2 := item; Inc(item2);
FillChar(props, sizeof(props), 0);
psc := nil;
ExcludedChars := '';
IncludedChars := '';
for i := 0 to data.itemcount - 2 do
begin
analysis.eScript_Etc := item.a.eScript_Etc;
analysis.s.uBidiLevel_Etc_ := item.a.s.uBidiLevel_Etc_;
hr := DoScriptShape(PWideChar(@data.Chars[item.iCharPos+1]), item2.iCharPos - item.iCharPos,
hfont, psc, temp_dc, analysis, logs, glyphs, visattr, glyphs_used);
if SUCCEEDED(hr) then
begin
if props.cBytes = 0 then
begin
props.cBytes := sizeof(props);
hr := ScriptGetFontProperties(0, psc, props);
if not SUCCEEDED(hr) then
begin
data.ExcludedFonts.Add(lpelfe.elfLogFont.lfFaceName);
Exit; // Ignore fonts that give us errors
end;
end;
// ... check for default glyph
for j := item.iCharPos to item2.iCharPos - 1 do
begin
nGlyph := PWordArray(logs)[j - item.iCharPos];
if (PWordArray(glyphs)[nGlyph] = props.wgDefault) or (PWordArray(glyphs)[nGlyph] = props.wgInvalid) or (PWordArray(glyphs)[nGlyph] = props.wgBlank) then
// I2421 - Test against blank and invalid glyphs as well as default
begin
if not Uni_IsSurrogate2(data.Chars[j+1]) then Inc(nDef); // I4828
ExcludedChars := ExcludedChars + data.Chars[j+1];
end
else
IncludedChars := IncludedChars + data.Chars[j+1];
if not Uni_IsSurrogate2(data.Chars[j+1]) then Inc(nTotal); // I4828
end;
FreeMem(visattr);
FreeMem(glyphs);
FreeMem(logs);
end
else
begin
{ I1484 - fix problem where single missing character could stop font from being matched }
ExcludedChars := ExcludedChars + Copy(data.Chars, item.iCharPos + 1, item2.iCharPos - item.iCharPos); // I2715
for j := item.iCharPos to item2.iCharPos - 1 do // I4828
begin
if not Uni_IsSurrogate2(data.Chars[j+1]) then
begin
Inc(nTotal);
Inc(nDef);
end;
end;
end;
Inc(item); Inc(item2);
end;
if (nTotal > 0) and ((nTotal-nDef) * 100 div nTotal > data.MinCoverage)
then data.Fonts.Add(lpelfe.elfLogFont.lfFaceName, (nTotal-nDef) * 100 div nTotal,IncludedChars,ExcludedChars)
else data.ExcludedFonts.Add(lpelfe.elfLogFont.lfFaceName);
finally
SelectObject(temp_dc, old_font);
ReleaseDC(0, temp_dc);
end;
finally
if psc <> nil then
ScriptFreeCache(psc);
DeleteObject(hfont);
end;
end;
function SortFonts(Item1, Item2: Pointer): Integer;
begin
Result := TFindFontResult(Item2).Coverage - TFindFontResult(Item1).Coverage; // best match first
if Result = 0 then
Result := WideCompareStr(TFindFontResult(Item1).FontName, TFindFontResult(Item2).FontName);
end;
procedure FindFontsForChars(const Chars: WideString; MinCoverage: Integer; Fonts: TFindFontList);
var
data: TFindFontEnumData;
lf: TLogFont;
hdc: THandle;
i: Integer;
begin
if Chars = '' then Exit;
try
hdc := GetDC(0);
try
data.Chars := Chars;
data.MinCoverage := MinCoverage;
data.Fonts := Fonts;
data.ExcludedFonts := TStringList.Create;
data.hdc := hdc;
if not DoScriptItemize(PWideChar(data.Chars), data.items, data.itemcount) then RaiseLastOSError;
//if not DoScriptLayout( ScriptLayout(data.itemcount - 1, directions,
try
data.outglyphs := AllocMem(SizeOf(WORD) * (Length(Chars) * 5));
try
FillChar(lf, sizeof(lf), 0);
lf.lfCharSet := DEFAULT_CHARSET;
EnumFontFamiliesEx(hdc, lf, @FindFontEnum, Integer(@data), 0);
finally
FreeMem(data.outglyphs);
end;
for i := 0 to data.ExcludedFonts.Count - 1 do
data.Fonts.Add(data.ExcludedFonts[i], -1, '', '');
finally
if data.items <> nil then
FreeMem(data.items);
data.ExcludedFonts.Free;
end;
finally
ReleaseDC(0, hdc);
end;
except
on E:Exception do
begin
//KL.Log(E.Message);
raise;
end;
end;
data.Fonts.Sort(SortFonts);
end;
{ TFindFontList }
function TFindFontList.Add(FontName: WideString; Coverage: Integer; IncludedChars, ExcludedChars: WideString): Integer;
var
f: TFindFontResult;
begin
f := TFindFontResult.Create;
f.FontName := FontName;
f.Coverage := Coverage;
f.IncludedChars := IncludedChars;
f.ExcludedChars := ExcludedChars;
Result := inherited Add(f);
end;
function TFindFontList.GetItem(Index: Integer): TFindFontResult;
begin
Result := inherited GetItem(Index) as TFindFontResult;
end;
function TFindFontList.IndexOfName(const Name: WideString): Integer;
var
i: Integer;
begin
for i := 0 to Count - 1 do
if WideSameText(Items[i].FontName, Name) then
begin
Result := i;
Exit;
end;
Result := -1;
end;
procedure TFindFontList.SetItem(Index: Integer; const Value: TFindFontResult);
begin
inherited SetItem(Index, Value);
end;
end.