2025年12月11日

Skia4Delphiでリソースとして埋め込まれたフォントを使用する

このアーティクルはDelphi Advent Calendar 2025の11日目の記事です。

ここまでリソースとして埋め込まれたフォントをVCL(GDI)で使用する方法を見てきました。ところで埋め込んだフォントをSkia4DelphiTSkLabelISkCanvasでも使用したいときは、少し違う方法が必要になります。
Skia4Delphiで描画に使用するフォントはTSkDefaultProviders.RegisterTypeface(かIFMXFontManagerService.AddCustomFontFromFile)で登録するのですが、このときのリソースタイプは(FONTではなく)RCDATAになっている必要があります。しかしフォント以外のリソースもリソースタイプRCDATAとして登録されるため、Win32APIのEnumResourceNames関数でプログラムに含まれるRT_RCDATAのリソースを列挙し、コールバック関数でリソースを読み出したら先頭4バイトでフォントデータかどうかを判別してからWin32APIのAddFontMemResourceEx関数とTSkDefaultProviders.RegisterTypefaceに渡すようにします。

まずプロジェクトにフォントをリソースとして追加します。Delphi IDEの"メインメニュー"→"プロジェクト"→"リソースと画像"で"<プロジェクト名>のリソース"ダイアログで、フォントをリソースとして追加します。このときリソースタイプを"RCDATA"に変更しておきます。
次にプログラムのなるべく早い時点でこのフォントを読み出して登録します。
unit LoadResFontsForSkia;

{$IFNDEF SKIA}
{$MESSAGE ERROR 'Enable Skia4Delphi.'}
{$ENDIF}

interface

uses
  Winapi.Windows,
  System.SysUtils, System.Classes,
  Vcl.Skia;

implementation

type
  TFontType = (ftNotFont, ftGDIFont, ftWebFont);

function CheckFontData(Stream: TStream): TFontType;
var
  Signature: DWORD;
begin
  Result := TFontType.ftNotFont;

  Stream.Position := 0;
  try
    if Stream.Read(Signature,SizeOf(Signature)) <> SizeOf(Signature) then
    begin
      Exit;
    end;

    case Signature of
      $00000100,  // TrueType
      $4F45454F,  // 'OTTO' OpenType
      $66637474:  // 'ttcf' TrueType Collection
      begin
        Result := TFontType.ftGDIFont;
      end;

      $46464F77,  // 'wOFF' Web Open Font
      $32464F77:  // 'wOF2' Web Open Font 2
      begin
        Result := TFontType.ftWebFont;
      end;
    end;

  finally
    Stream.Position := 0;
  end;
end;

function EnumResNameProc(hModule: HMODULE; lpszType: PChar; lpszName: PChar; lParam: LONG_PTR): BOOL; stdcall;
var
  RS: TResourceStream;
  NumFonts: DWORD;
  FontType: TFontType;
begin
  { Load resource }
  if Is_IntResource(lpszName) = False then
  begin
    { By name }
    RS := TResourceStream.Create(HInstance,String(lpszName),RT_RCDATA);
  end
  else
  begin
    { By index }
    RS := TResourceStream.CreateFromID(HInstance,NativeUInt(lpszName),RT_RCDATA);
  end;

  try
    FontType := CheckFontData(RS);
    if FontType in [TFontType.ftGDIFont] then
    begin
      { Regsiter font to GDI }
      if AddFontMemResourceEx(RS.Memory,RS.Size,nil,@NumFonts) = 0 then
      begin
        RaiseLastOSError;
      end;
    end;

    if FontType in [TFontType.ftGDIFont, TFontType.ftWebFont] then
    begin
      { Register font to Skia4Delphi }
      TSkDefaultProviders.RegisterTypeface(RS);
    end;

  finally
    RS.Free;
  end;

  Result := True;
end;

procedure LoadResourceFonts;
begin
  if EnumResourceNames(HInstance,RT_RCDATA,@EnumResNameProc,0) = False then
  begin
    RaiseLastOSError;
  end;
end;

initialization
  LoadResourceFonts;

end.
ユニットのinitialization部で呼び出しているLoadResourceFonts関数では、Win32APIのEnumResourceNames関数でプログラムに含まれるRT_RCDATAのリソースを列挙します。コールバック関数EnumResNameProcではコンストラクタTResourceStream.Createを呼び出してフォントをストリームに読み出し、フォントデータかどうかの判定関数CheckFontDataを呼び出します。CheckFontData関数ではストリームから先頭4バイトを読み出し、TrueType(.ttf)、TrueType Collection(.ttc)OpenType(.otf)、OpenType Collection(.otc)WOFF(.woff)、WOFF2(.woff2)のそれぞれのシグネチャに一致するかどうかでフォントデータかどうかの判定を行います。GDIでは使用できないWOFF/WOFF2形式のフォントもSkia4Delphiでは使用できるので、WOFF/WOFF2形式のときはAddFontMemResourceEx関数に加えてTSkDefaultProviders.RegisterTypefaceも呼び出すようにしています。
これで登録したフォントはSkia4DelphiのFont.Familiesにそのフォント名を指定することで使用できます。TSkLabelならTSkLabel.TextSettings.Font.Families、ISkCanvasに対する描画ならTSkTypeface.MakeFromNameの第1パラメータです。

ん?リソースタイプFONTではなくRCDATAで埋め込んだフォントデータをAddFontMemResourceEx関数に渡せる?じゃあ前回のアーティクルのようにリソースコンパイラを差し替えたりせず、RCDATAでやればいいのでは?と思うかもしれません。その通りで、今回の方法(フォントをRCDATAで埋め込む)を使えば、Delphi 12.3およびそれ以前でもビルド前イベントでresinatorを使って処理する必要はありません。しかしプログラムにフォント以外のRCDATAのリソースが埋め込まれていた場合は、それらもすべて読み出してフォントデータかどうかの判別をする必要があります。このオーバヘッドを許容できるのであれば、今回の方法でも問題ありません。

0 件のコメント: