winapiunicodefontsdelphi-6

GetFontUnicodeRanges without a window


Is there a chance to call GetFontUnicodeRanges without a window? For example, it could be a Windows service not permitted to interact with desktop.

Currently I am testing this with console application:

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.

Solution

  • In Windows GDI, you can create a device context and select a font into it without needing a handle to a window. E.g.,

            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));
    

    Note that GDI text functions use the UTF-16 encoding, and all of them were created before Unicode had assigned any supplementary-plane characters. As a result, functions that take or return lists of characters that are not a string, such as GetFontUnicodeRanges, don't work well for much of Unicode today. GetFontUnicodeRanges returns a pointer to a GLYPHSET, which has an array of WCRANGE structs. That has a single WCHAR to represent a Unicode character. As a result, GetFontUnicodeRanges has no way to report any Unicode supplementary-plane characters. In some fonts, that might be the majority of characters supported in the font.

    In this regard, GDI is not just ancient, but also obsolete. For what you're doing, DirectWrite is a better option: all of its APIs support all Unicode characters.

    The DWrite method you want is 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 depending upon the source of the font you're interested in—could be an installed font, a custom font set, a memory blob, or a font file.