unit mainunit; 

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Graphics, Dialogs, StdCtrls, Grids, IniFiles,
  LCLType, LCLIntf, LazUTF8;

type

  { TfrmMain }

  TfrmMain = class(TForm)
    btnResetText: TButton;
    btnApplyFilter: TButton;
    btnFontDlg: TButton;
    cbCharset: TComboBox;
    cbPitch: TComboBox;
    chkStrike: TCheckBox;
    chkUnderLine: TCheckBox;
    FontDialog1: TFontDialog;
    lflFontFaceList: TLabel;
    lblStyles: TLabel;
    lblCharset: TLabel;
    lblFilter: TLabel;
    lblSizes: TLabel;
    lbFamily: TListBox;
    lbStyles: TListBox;
    lbSizes: TListBox;
    lbCharset: TListBox;
    grid: TStringGrid;
    procedure btnFontDlgClick(Sender: TObject);
    procedure btnResetTextClick(Sender: TObject);
    procedure btnApplyFilterClick(Sender: TObject);
    procedure chkStrikeChange(Sender: TObject);
    procedure chkUnderLineChange(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: boolean);
    procedure FormCreate(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure lbFamilyClick(Sender: TObject);
    procedure lbCharsetClick(Sender: TObject);
    procedure lbSizesClick(Sender: TObject);
    procedure lbStylesClick(Sender: TObject);
  private
    FTime: LongWord;
    FIniTime: LongWord;
    FCurrentFamily,FCurrentStyle,FCurrentSize,FCurrentCharset: string;
    procedure StartTimer;
    Procedure EndTimer;
    function  GetCharSet: byte;
    function  GetPitch: integer;
    procedure EnableEvents(Ok: boolean; Lb: TListbox = nil);
    procedure SelectFont;
    procedure ResetSampleText;
    procedure SaveSelection;
    procedure RestoreSelection(Sender: TListbox);
    procedure LoadFontList;
    procedure LoadFamilyFonts(Charset: integer);
    procedure UpdateFont(F: TFont);
  public

  end; 

var
  frmMain: TfrmMain;

implementation
{.$define Debug}

{$R *.lfm}

{ TfrmMain }
var
  LStyles,
  LSizes: TStringList;

function EnumFontsNoDups(
  var LogFont: TEnumLogFontEx;
  var Metric: TNewTextMetricEx;
  FontType: Longint;
  Data: LParam):LongInt; stdcall;
var
  L: TStringList;
  S: String;
begin
  L := TStringList(ptrint(Data));
  S := LogFont.elfLogFont.lfFaceName;
  if L.IndexOf(S)<0 then
    L.Add(S);
  result := 1;
end;

var
  NeedTTF: boolean;
  
function EnumFamilyFonts(
  var eLogFont: TEnumLogFontEx;
  var Metric:TNewTextMetricEx;
  FontType:longint;
  Data:LParam):longint; stdcall;
var
  s: string;
  n: integer;
  lcharsets: TStringList;
begin
  LCharSets := TStringList(ptrint(Data));
  if Lcharsets<>nil then begin
    // collect charsets
    // when collecting charsets no need to collect all other info
    s :=CharSetToString(eLogFont.elfLogFont.lfCharSet);
    if LCharsets.indexOf(s)<0 then
      LCharsets.AddObject(s, TObject(ptrint(eLogFont.elfLogFont.lfCharSet)));
    exit;
  end;
  
  // collect styles
  s :=eLogFont.elfStyle;
  if LStyles.IndexOf(s)<0 then begin
    // encode bold (bit 0), italic (bit 1) -- see SelectFont
    n := 0;
    {$IF DEFINED(LCLWin32) or DEFINED(LCLCocoa) }
    if (eLogFont.elfLogFont.lfItalic <> 0) then
      n := n or 1;
    if (eLogFont.elfLogFont.lfWeight > FW_MEDIUM) then
      n := n or 2;
    {$ENDIF}
    {$IF DEFINED(LCLGtk2) or DEFINED(LCLGtk3) or DEFINED(LCLQt) or DEFINED(LCLQt5)}
    s := Lowercase(s);
    if (pos('italic', s) <> 0) or (pos('oblique', s) <> 0) then
      n := n or 1;
    if (pos('bold', s) <> 0) then
      n := n or 2;
    {$ENDIF}
    LStyles.AddObject(eLogFont.elfStyle, TObject(ptrint(n)));
  end;
  
  // collect sizes
  if FontType=TRUETYPE_FONTTYPE then
    NeedTTF := True
  else
    with metric.ntmentm do
      if tmDigitizedAspectY <> 0 then begin
        n := (tmHeight-tmInternalLeading)*72+tmDigitizedAspectY shr 1;
        n := n div tmDigitizedAspectY;
        if n>0 then begin
          s := IntToStr(n)+'*'; // font sizes with * indicate raster fonts
          if LSizes.IndexOf(s)<0 then
            LSizes.AddObject(s, TObject(ptrint(n)));
        end;
      end;
  result := 1;
end;

procedure TfrmMain.btnFontDlgClick(Sender: TObject);
begin
  if FontDialog1.Execute then
    UpdateFont(FontDialog1.Font);
end;

procedure TfrmMain.btnResetTextClick(Sender: TObject);
begin
  ResetSampleText;
end;

procedure TfrmMain.btnApplyFilterClick(Sender: TObject);
begin
  LoadFontList;
end;

procedure TfrmMain.chkStrikeChange(Sender: TObject);
begin
  SelectFont;
end;

procedure TfrmMain.chkUnderLineChange(Sender: TObject);
begin
  SelectFont;
end;

procedure TfrmMain.FormCloseQuery(Sender: TObject; var CanClose: boolean);
var
  Ini: TInifile;
begin
  SaveSelection;
  Ini := TIniFile.Create(UTF8ToSys(ChangeFileExt(Application.ExeName,'.ini')));
  try
    Ini.WriteString('General','CurrentFamily', FCurrentFamily);
    Ini.WriteString('General','CurrentCharset',FCurrentCharset);
    Ini.WriteString('General','CurrentStyle',  FCurrentStyle);
    Ini.WriteString('General','CurrentSize',   FCurrentSize);
  finally
    Ini.Free;
  end;
end;

procedure TfrmMain.FormCreate(Sender: TObject);
  procedure Add(Charset: Integer);
  begin
    cbCharset.Items.AddObject(CharSetToString(CharSet), TObject(ptrint(Charset)));
  end;
var
  Ini: TIniFile;
begin
  // populate cbcharset
  cbCharset.Items.clear;
  Add(ANSI_CHARSET);
  Add(DEFAULT_CHARSET);
  Add(SYMBOL_CHARSET);
  Add(MAC_CHARSET);
  Add(SHIFTJIS_CHARSET);
  Add(HANGEUL_CHARSET);
  Add(JOHAB_CHARSET);
  Add(GB2312_CHARSET);
  Add(CHINESEBIG5_CHARSET);
  Add(GREEK_CHARSET);
  Add(TURKISH_CHARSET);
  Add(VIETNAMESE_CHARSET);
  Add(HEBREW_CHARSET);
  Add(ARABIC_CHARSET);
  Add(BALTIC_CHARSET);
  Add(RUSSIAN_CHARSET);
  Add(THAI_CHARSET);
  Add(EASTEUROPE_CHARSET);
  Add(OEM_CHARSET);
  Add(FCS_ISO_10646_1);
  Add(FCS_ISO_8859_1);
  Add(FCS_ISO_8859_2);
  Add(FCS_ISO_8859_3);
  Add(FCS_ISO_8859_4);
  Add(FCS_ISO_8859_5);
  Add(FCS_ISO_8859_6);
  Add(FCS_ISO_8859_7);
  Add(FCS_ISO_8859_8);
  Add(FCS_ISO_8859_9);
  Add(FCS_ISO_8859_10);
  Add(FCS_ISO_8859_15);
  ResetSampleText;
  
  Ini := TIniFile.Create(UTF8ToSys(ChangeFileExt(Application.ExeName,'.ini')));
  try
    FCurrentFamily  := Ini.ReadString('General','CurrentFamily', '');
    FCurrentCharset := Ini.ReadString('General','CurrentCharset','');
    FCurrentStyle   := Ini.ReadString('General','CurrentStyle',  '');
    FCurrentSize    := Ini.ReadString('General','CurrentSize',   '');
  finally
    Ini.Free;
  end;
end;

procedure TfrmMain.FormShow(Sender: TObject);
begin
  LoadFontlist;
  lbCharsetClick(nil);
  SelectFont;
end;

procedure TfrmMain.lbFamilyClick(Sender: TObject);
begin
  LoadFamilyFonts(-1);
  lbCharsetClick(nil);
  SelectFont;
end;

procedure TfrmMain.lbCharsetClick(Sender: TObject);
var
  i: Integer;
begin
  i := lbCharset.ItemIndex;
  if i<0 then exit;
  i := ptrint(lbCharSet.Items.Objects[i]);
  LoadFamilyFonts(byte(i));
end;

procedure TfrmMain.lbSizesClick(Sender: TObject);
begin
  SelectFont;
end;

procedure TfrmMain.lbStylesClick(Sender: TObject);
begin
  SelectFont;
end;

procedure TfrmMain.StartTimer;
begin
  FIniTime := GetTickCount;
end;

procedure TfrmMain.EndTimer;
begin
  FTime := GetTickCount-FIniTime;
end;

function TfrmMain.GetCharSet: Byte;
begin
  if cbCharSet.Itemindex<0 then
    result := ANSI_CHARSET
  else
    result := byte(ptrint(cbCharset.items.Objects[CbCharset.ItemIndex]));
end;

function TfrmMain.GetPitch: integer;
begin
  case cbPitch.ItemIndex of
    1: result := FIXED_PITCH;
    2: result := VARIABLE_PITCH;
    3: result := MONO_FONT;
    else
      result := DEFAULT_PITCH;
  end;
  btnApplyFilter.Caption := IntToStr(result);
end;

procedure TfrmMain.EnableEvents(Ok: boolean; Lb: TListbox = nil);
  procedure SetEvent(L: TListbox);
  var
    Event: TNotifyEvent;
  begin
    Event := nil;
    if ok then begin
      if l=lbFamily then Event := @lbFamilyClick else
      if l=lbStyles then Event := @LbStylesClick else
      if l=lbCharset then Event := @lbCharsetClick else
      if l=lbSizes then Event := @lbSizesClick;
    end;
    L.OnClick := Event;
  end;
begin
  if Lb<>nil then
    SetEvent(Lb)
  else begin
    SetEvent(lbFamily);
    SetEvent(lbStyles);
    SetEvent(lbCharset);
    SetEvent(lbSizes);
  end;
end;

procedure TfrmMain.SelectFont;
var
  F: TFont;
  i: integer;
  function GetFontSize(s: string): Integer;
  begin
    i := pos('*',s);
    if i<>0 then
      result := StrToInt(Copy(S, 1, i-1))
    else
      result := StrToInt(s);
  end;
begin
  if lbFamily.ItemIndex>=0 then
    if lbCharSet.ItemIndex>=0 then
      if lbStyles.ItemIndex>=0 then
        if lbSizes.ItemIndex>=0 then
        begin
          F := TFont.Create;
          try
            F.Name := lbFamily.Items[lbFamily.ItemIndex];
            F.CharSet := TFontCharSet(ptrint(lbCharSet.Items.Objects[lbCharset.ItemIndex]));
            F.Size := GetFontSize(lbSizes.Items[lbSizes.ItemIndex]);
            i := ptrint(lbStyles.Items.Objects[lbStyles.ItemIndex]);
            F.Style := [];
            if i and 1 <> 0 then F.Style := F.Style + [fsItalic];
            if i and 2 <> 0 then F.Style := F.Style + [fsBold];
            if chkUnderLine.Checked then F.Style := F.Style + [fsUnderline];
            if chkStrike.Checked then F.Style := F.Style + [fsStrikeOut];
            UpdateFont(F);
            SaveSelection;
          finally
            F.Free;
          end;
        end;
end;

procedure TfrmMain.ResetSampleText;
var
  L: TStringList;
begin
  L := TStringList.Create;
  L.Add('abcdefghijklmnopqrstuvwxyz');
  L.Add('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
  L.Add('01234567891   ўЈ¤Ґ§');
  L.Add('абвгдежзийклмнопрстуфхцшщъыь');
  L.add('АБВГДЕЖЗИЙКЛМНОПРСТУФХХШЩЪЫЬЭЯ');
  grid.Cols[0] := L;
  l.Free;
end;

procedure TfrmMain.SaveSelection;
  function doGet(lb: TListbox): string;
  begin
    if lb.itemindex>=0 then
      result := lb.Items[lb.ItemIndex]
    else
      result := '';
  end;
begin
  FCurrentFamily := doGet(LbFamily);
  FCurrentCharset := doGet(LbCharset);
  FCurrentStyle := doGet(LbStyles);
  FCurrentSize := doGet(LbSizes);
end;

procedure TfrmMain.RestoreSelection(Sender: TListbox);
  function GetSelection: string;
  begin
    if Sender.itemindex>=0 then
      result := Sender.Items[Sender.ItemIndex]
    else
      result := '';
  end;
  function GetCurrent: string;
  begin
    if Sender=lbFamily then result := FCurrentFamily else
    if Sender=lbCharset then result := FCurrentCharset else
    if Sender=lbStyles then result := FCurrentStyle else
    if Sender=lbSizes then result := FCurrentSize;
  end;
var
  i: Integer;
  s: string;
begin
  s := GetCurrent;
  if GetSelection <> s then begin
    i := Sender.Items.IndexOf(s);
    if i>-1 then begin
      {$ifdef debug}
      debugln('RestoreSelection: listbox=',Sender.Name,' Old=',GetSelection,' New=',S);
      {$endif}
      if i<>Sender.ItemIndex then
        Sender.ItemIndex := i;
    end;
  end;
end;

procedure TfrmMain.LoadFontList;
var
  DC: HDC;
  lf: TLogFont;
  L: TStringList;
  i: Integer;
begin
  // this could be have done also with screen.fonts
  // but here, we have the list filtered by Charset
  lf.lfCharSet := GetCharSet;
  lf.lfFaceName := '';
  case cbPitch.ItemIndex of
    1: i:=FIXED_PITCH;
    2: i:=VARIABLE_PITCH;
    3: i:=MONO_FONT;
    else
      i:=DEFAULT_PITCH;
  end;
  lf.lfPitchAndFamily := i;

  {$ifdef debug}
  debugln('LoadFontList: for charset=',CharSetToString(lf.lfcharset));
  {$endif}

  L := TStringList.create;
  lbStyles.Clear;
  lbCharset.Clear;
  lbSizes.Clear;

  DC := GetDC(0);
  EnableEvents(False, lbFamily);
  try
    StartTimer;
    EnumFontFamiliesEX(DC, @lf, @EnumFontsNoDups, ptrint(L), 0);
    EndTimer;
    L.Sort;
    lbFamily.Items.Assign(L);
    lbFamily.Itemindex := -1;
    
    RestoreSelection(lbFamily);
    if lbFamily.ItemIndex<0 then begin
      if lbFamily.Items.Count>0 then
        lbFamily.ItemIndex := 0;
    end;
    LoadFamilyFonts(-1);
    
    lflFontFaceList.Caption := format('Fontfaces, found %d, %d ms',[lbFamily.Items.Count, FTime]);
  finally
    EnableEvents(True, lbFamily);
    ReleaseDC(0, DC);
    L.Free;
  end;
end;

function CompareSizes(List: TStringList; Index1, Index2: Integer): Integer;
begin
  result := ptrint(List.Objects[Index1]) - ptrint(List.Objects[Index2]);
end;

procedure TfrmMain.LoadFamilyFonts(Charset: integer);
var
  LCharset: TStringList;
  dc: HDC;
  Lf: TLogFont;
  i: LongInt;
  LoadingCharsets: boolean;
  
  procedure AddScalableSizes;
    procedure Add(Sz: Integer);
    begin
      if LSizes.IndexOfObject(TObject(ptrint(Sz)))<0 then
        LSizes.AddObject(IntToStr(Sz), TObject(ptrint(Sz)));
    end;
  begin
    add(8);  add(9);  add(10); add(11); add(12); add(14); add(16); add(18);
    add(20); add(22); add(24); add(26); add(28); add(36); add(48); add(72);
  end;
begin
  i := lbFamily.ItemIndex;
  if i<0 then exit;
  
  LoadingCharsets := Charset<0;
  {$ifdef debug}
  Write('LoadFamilyFonts: for family=', lbFamily.Items[i],' and Charset=');
  if LoadingCharsets then
    debugln('ALL_CHARSETS')
  else
    debugln(CharsetToString(byte(Charset)));
  {$endif}

  // at the moment only global fonts are enumerated
  // ie. fonts selected in a device context are not enumerated
  DC := GetDC(0);
  // create global variables, EnumFamilyFonts use them
  if LoadingCharsets then begin
    // need to fill charset listbox too
    LCharset := TStringList.Create;
    CharSet := DEFAULT_CHARSET;
  end else begin
    // charset listbox is already filled, so fill styles and sizes
    LCharSet := nil;
    LStyles := TStringList.Create;
    LSizes  := TStringList.Create;
  end;
  try
    // enumerate fonts
    Lf.lfFaceName := lbFamily.Items[i];
    Lf.lfCharSet := byte(Charset);
    Lf.lfPitchAndFamily := 0;
    NeedTTF := False;
    EnumFontFamiliesEX(DC, @Lf, @EnumFamilyFonts, ptrint(LCharset), 0);
    // fill charset listbox if necessary
    if LCharset<>nil then begin
      LCharset.Sort;
      EnableEvents(False, LbCharset);
      LbCharset.Items.Assign(LCharset);
      LbCharset.ItemIndex := -1;
      EnableEvents(true, LbCharset);
    end else begin
      // fill styles listbox
      LStyles.Sort;
      EnableEvents(False, LbStyles);
      LbStyles.Items.Assign(LStyles);
      lbStyles.ItemIndex := -1;
      EnableEvents(true, LbStyles);
      RestoreSelection(lbStyles);
      if lbStyles.ItemIndex<0 then begin
       if LbStyles.Items.Count>0 then
          LbStyles.ItemIndex := 0;
      end;
      // fill sizes listbox
      // any raster font size is already there
      if NeedTTF then
        AddScalableSizes;
      LSizes.CustomSort(@CompareSizes);
      EnableEvents(False, lbSizes);
      lbSizes.Items.Assign(LSizes);
      lbSizes.ItemIndex := -1;
      EnableEvents(true, LbSizes);
      RestoreSelection(LbSizes);
      if lbSizes.ItemIndex<0 then begin
        if lbSizes.Items.Count>0 then
          LbSizes.ItemIndex := 0;
      end;
    end;
  finally
    if LCharset=nil then begin
      LSizes.Free;
      LStyles.Free;
    end else
      LCharset.Free;
    releaseDC(0, DC);
  end;
  
  if LoadingCharsets then begin
    // make an initial charset selection
    RestoreSelection(lbCharset);
    if lbCharset.ItemIndex<0 then begin
      if lbCharset.Items.Count>0 then
        lbCharset.ItemIndex := 0;
    end;
  end;
end;

procedure TfrmMain.UpdateFont(F: TFont);
begin
  grid.Font := F;
  grid.DefaultRowHeight := grid.canvas.textHeight('Бj') + 5;
end;

end.

