没有 window 的 GetFontUnicodeRanges

GetFontUnicodeRanges without a window

有机会在没有 window 的情况下调用 GetFontUnicodeRanges 吗?例如,它可能是不允许与桌面交互的 Windows 服务。

目前我正在使用控制台应用程序对此进行测试:

program UnicodeConsoleOutput;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Windows;

var
  NumWritten: DWORD;
  Text: WideString;
  u8s: UTF8String;

procedure Add(AStart, AEnd: Word);
var
  i: Word;
begin
  Text := Text + WideFormat('[%x...%x]:'#13#10, [AStart, AEnd]);
  for i := AStart to AEnd do
    Text := Text + WideChar(i);
  Text := Text + WideString(#13#10#13#10);
end;

//Actually I want to get glyph ranges for "Consolas" font
procedure GetFontRanges();
type
  TRangesArray = array[0..(MaxInt div SizeOf(TWCRange)) - 1] of TWCRange;

  PRangesArray = ^TRangesArray;
const
  ConsoleTitle = '{A46DD332-0D57-4310-B91E-A68957C20429}';
var
  GS: PGlyphSet;
  GSSize: LongWord;
  i: Integer;
  rng: TWCRange;
  hConsole: HWND;
  hDev: HDC;
begin
  //A dirty hack to get console window handle suggested by Microsoft
  SetConsoleTitle(PChar(ConsoleTitle));
  hConsole := FindWindow(nil, PChar(ConsoleTitle));
  
  hDev := GetDC(hConsole);
  try

    GSSize := GetFontUnicodeRanges(hDev, nil);
    GetMem(Pointer(GS), GSSize);
    try
      GS.cbThis := GSSize;
      GS.flAccel := 0;
      GS.cGlyphsSupported := 0;
      GS.cRanges := 0;
      if GetFontUnicodeRanges(hDev, GS) <> 0 then begin
        for i := 0 to GS.cRanges - 1 do begin
          rng := PRangesArray(@GS.ranges)[i];
          Add(Word(rng.wcLow), Word(rng.wcLow) + rng.cGlyphs - 1);
        end;
      end;
    finally
      FreeMem(Pointer(GS), GSSize);
    end;
  finally
    ReleaseDC(hConsole, hDev);
  end;
end;

begin
  try
    GetFontRanges();
    SetConsoleOutputCP(CP_UTF8);
    u8s := UTF8Encode(Text);
    WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), PChar(u8s), Length(u8s),
      NumWritten, nil);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn;
end.

在 Windows GDI 中,您可以创建设备上下文和 select 字体,而不需要 window 的句柄。例如,

        HDC hdc = CreateDC(L"DISPLAY", NULL, NULL, NULL);
        //CreateCompatibleDC(NULL) also works

        HFONT hFont = CreateFont(
            -20, 0, 0, 0,
            FW_REGULAR,
            FALSE, FALSE, FALSE, DEFAULT_CHARSET,
            OUT_DEFAULT_PRECIS,
            CLIP_DEFAULT_PRECIS,
            DEFAULT_QUALITY,
            DEFAULT_PITCH || FF_DONTCARE,
            L"Arial"
        );

        HFONT oldFont = static_cast<HFONT>(SelectObject(hdc, hFont));

请注意,GDI 文本函数使用 UTF-16 编码,并且所有这些函数都是在 Unicode 分配任何补充平面字符之前创建的。因此,采用 or return 非字符串字符列表的函数(例如 GetFontUnicodeRanges)对于当今的大部分 Unicode 来说效果不佳。 GetFontUnicodeRanges returns a pointer to a GLYPHSET, which has an array of WCRANGE 结构。它有一个 WCHAR 来表示一个 Unicode 字符。因此,GetFontUnicodeRanges 无法报告任何 Unicode 增补平面字符。在某些字体中,这可能是该字体支持的大多数字符。

在这方面,GDI不仅古老,而且过时了。对于您正在做的事情,DirectWrite 是一个更好的选择:它的所有 API 都支持所有 Unicode 字符。

您想要的 DWrite 方法是 IDWriteFontFace1::GetUnicodeRanges. Many DWrite APIs, including this, can be used without a window or even a device context. You'll probably want to obtain the IDWriteFontFace1 object by calling IDWriteFont::CreateFontFace, IDWriteFactory::CreateFontFace or IDWriteFontFaceReference::CreateFontFace,具体取决于您感兴趣的字体来源 — 可以是已安装的字体、自定义字体集、内存块或字体文件。