読者です 読者をやめる 読者になる 読者になる

EMF+で文字をレンダリングしているのは?

EMF+のレコードをデバッガで追っていて、面白いことを発見した。GDI+では、文字の描画は、Graphics::DrawStringメソッドを使うのだが、実は、その内部では、ExtTextOutが使われているのだ。ということは、逆に言えば、フォントのアンチエイリアスは、CreateFont/CreateFontIndirectしているところで、LOGFONT構造体のlfQualityをいじって、強制的にアンチエイリアスをOFFにしてしまえば良いわけだ。そして、どうやら、EmfRecordTypeExtCreateFontIndirect (EMR_EXTCREATEFONTINDIRECTW/構造体だと、EMREXTCREATEFONTINDIRECTW)を処理すればこれができるらしい。


ということで書いてみたコードは次のような感じ。

Graphics g(...);
Metafile mf(...);
Rect rc(...); // レンダリングしたいサイズで初期化

// 事前にアンチエイリアスをOFFにしておく
g.SetSmoothingMode(Gdiplus::SmoothingModeNone);
g.SetTextRenderingHint(Gdiplus::TextRenderingHintSingleBitPerPixelGridFit);

// 描画
g.EnumerateMetafile(mf, rc, emfProc, &mf, NULL);

そして、本体から呼び出されるコールバックは次のような感じ。

static BOOL CALLBACK emfProc(
  Gdiplus::EmfPlusRecordType recordType,
  unsigned int flags,
  unsigned int dataSize,
  const unsigned char* pStr,
  void* callbackData)
{
  BYTE* buf = NULL;
  
  Metafile* mf = (Metafile*)callbackData;
  if(recordType == Gdiplus::EmfPlusRecordTypeSetAntiAliasMode)
  {
    // 無視する
    return TRUE;
  }
  else if(recordType == EmfRecordTypeExtCreateFontIndirect)
  {
    // pStrを直接書き換えるのはリスクが高いので、
    // コピーしてから書き換え、それをPlayRecordに渡す。
    buf = new BYTE[dataSize + 8];
    pStr = buf;

    CopyMemory(buf, pStr - 8, dataSize + 8);
    EMREXTCREATEFONTINDIRECTW& eecfi = *(EMREXTCREATEFONTINDIRECTW*)buf;
    
    // アンチエイリアスを強制的にOFFにする
    eecfi.elfw.elfLogFont.lfQuality = NONANTIALIASED_QUALITY;
  }
  
  BOOL ret = mf->PlayRecord(recordType, flags, dataSize, pStr);
  
  if(buf) delete[] buf; // バッファ解放
  
  return ret;
}