Win32编程点滴2

文章来源:http://www.cnblogs.com/Greatest/archive/2009/08/31/1557422.html

前段时间我一直在研究一个问题:在一个DC中选择一个英文字体,为什么还是能够正确的绘制出中文?然后,我就找到了这篇文章,觉得还蛮好看的,所以翻译了一下。虽然这篇2004年写的文章,即使放到当年来看技术上也是很老了。从windows2000开始,类似TextOut等gdi函数的实现就使用了比本文提及的mlang库更加高级的uniscribe库。现在的TextOut,无论font linking,还是什么从右到左显示的阿拉伯文字,还有各式各样的unicode显示问题,都能应付。所以,这篇文章看点并不在于技术有多高级,而是涉及的技术的有趣性,还有作为windows作者,写出的函数的严谨性。好了,不多说了,正文开始。(可能文章比较老,根据我查msdn,原文好像有几个错误,不过我都保留了原来的意思,没有修正。)

How to display a string without those ugly boxes


当你尝试绘制一个字符串,而当前字体不包含它的所有字符时,你会看见一些难看的方块。那些字体中不包含的字符将显示为一个难看的方块。
我们从这个示例程序开始,把下面代码加入到PaintContent函数中:

void
PaintContent(HWND hwnd, PAINTSTRUCT *pps)
{
    TextOutW(pps->hdc, 0, 0,
            L"ABC\x0410\x0411\x0412\x0E01\x0E02\x0E03", 9);

}

该字符串包含了三个不同的字母表里的前三个字母:罗马字母“ABC”,西里尔字母“АБВ”和泰国字母“กขฃ”。

如果你运行这个程序,对那些非罗马字母你会看到一堆难看的方块,因为SYSTEM字体支持的字符集非常有限。

但如何选择合适的字体?如果该字符串包含韩语或日语的字符呢?没有一个单独的字体,包含了Unicode定义的全部字符。(至少,常见的字体中没有。)你会怎么做?

这就引入了font linking技术。

Font linking技术允许你将一个字符串分成几块,每一块用一个合适的字体来绘制。

IMLangFontLink2接口对这样的字符串分块提供了必要的方法。GetStrCodePages将字符串分割成块,每一块中的每个字符能用同一种字体来绘制,而MapFont创建这个字体。

好了,让我们来写带有font linking技术的TextOut函数。我们将一步一步完成这个函数,首先看核心的部分。

#include <mlang.h>

HRESULT TextOutFL(HDC hdc, int x, int y, LPCWSTR psz, int cch)
{
  ...
  while (cch > 0) {
    DWORD dwActualCodePages;
    long cchActual;
    pfl->GetStrCodePages(psz, cch, 0, &dwActualCodePages, &cchActual);
    HFONT hfLinked;
    pfl->MapFont(hdc, dwActualCodePages, 0, &hfLinked);
    HFONT hfOrig = SelectFont(hdc, hfLinked);
    TextOut(hdc, ?, ?, psz, cchActual);
    SelectFont(hdc, hfOrig);
    pfl->ReleaseFont(hfLinked);
    psz += cchActual;
    cch -= cchActual;
  }
  ...
}

在计算出当前默认字体所支持的代码页之后,我们通过GetStrCodePages以块为单位来遍历字符串。对每一块,我们创建一个字体,在“正确的地方”绘制出这块的字符。重复如此,直至字符串结束。

剩下的就是完善和文书工作。

首先,什么是“正确的地方”?我们希望下一块能接着上一块的地方显示。为此,我们利用TA_UPDATECP文本对齐模式,此模式要求GDI在当前位置显示文本,并将当前位置更新为文本结尾处(也就是下一块的位置)。

因此,文书工作的一部分是设置DC的当前位置并设置文本模式为TA_UPDATECP:

  SetTextAlign(hdc, GetTextAlign(hdc) | TA_UPDATECP);
  MoveToEx(hdc, x, y, NULL);

然后,我们将坐标“0,0”传给TextOut,因为如果文本对齐模式是TA_UPDATECP的话,TextOut忽略坐标参数,而在当前位置绘制文本。

当然,我们不能这样乱改DC的设置。如果调用者并没有设置TA_UPDATECP,那么调用者也不会希望我们改变当前位置。因此,我们保存下当前位置并回复它(以及原来的文本对齐模式)。

  POINT ptOrig;
  DWORD dwAlignOrig = GetTextAlign(hdc);
  SetTextAlign(hdc, dwAlignOrig | TA_UPDATECP);
  MoveToEx(hdc, x, y, &ptOrig);
  while (cch > 0) {
    ...
    TextOut(hdc, 0, 0, psz, cchActual);
    ...
  }
  // if caller did not want CP updated, then restore it
  // and restore the text alignment mode too
  if (!(dwAlignOrig & TA_UPDATECP)) {
    SetTextAlign(hdc, dwAlignOrig);
    MoveToEx(hdc, ptOrig.x, ptOrig.y, NULL);
  }


下一步是完善:利用GetStrCodePages的第二个参数,它指定了优先使用的代码页。显然我们应该优先使用我们想要的那个字体,那样如果用这个字体能直接显示,那就不必用另外的字体了。

  HFONT hfOrig = (HFONT)GetCurrentObject(hdc, OBJ_FONT);
  DWORD dwFontCodePages = 0;
  pfl->GetFontCodePages(hdc, hfOrig, &dwFontCodePages);
  ...
  while (cch > 0) {
    pfl->GetStrCodePages(psz, cch, dwFontCodePages, &dwActualCodePages, &cchActual);
    if (dwActualCodePages & dwFontCodePages) {
      // our font can handle it - draw directly using our font
      TextOut(hdc, 0, 0, psz, cchActual);
    } else {
      ... MapFont etc ...
    }
  }
  ...


当然,你可能想知道这个神奇的pfl哪来的。它来自于mlang的Multilanguage对象。

  IMLangFontLink2 *pfl;
  CoCreateInstance(CLSID_CMultiLanguage, NULL,
                   CLSCTX_ALL, IID_IMLangFontLink2, (void**)&pfl);
  ...
  pfl->Release();

当然,我们忽略了所有的错误处理。当遍历了字符串的几块后,产生了一个错误,这将是个问题。怎么办?

当产生错误的时候,我使用原来的字体显示字符串。我们不能擦除已经绘制的字符,也不能只绘制一半的字符串(调用者显然不知道如何继续)因此,我们使用原来的字体绘制,并希望它能工作的很好。毕竟,这样也不会比原来没有font linking技术跟糟糕吧。

完善后,我们得到最终的函数:

HRESULT TextOutFL(HDC hdc, int x, int y, LPCWSTR psz, int cch)
{
  HRESULT hr;
  IMLangFontLink2 *pfl;
  if (SUCCEEDED(hr = CoCreateInstance(CLSID_CMultiLanguage, NULL,
                      CLSCTX_ALL, IID_IMLangFontLink2, (void**)&pfl))) {
    HFONT hfOrig = (HFONT)GetCurrentObject(hdc, OBJ_FONT);
    POINT ptOrig;
    DWORD dwAlignOrig = GetTextAlign(hdc);
    if (!(dwAlignOrig & TA_UPDATECP)) {
      SetTextAlign(hdc, dwAlignOrig | TA_UPDATECP);
    }
    MoveToEx(hdc, x, y, &ptOrig);
    DWORD dwFontCodePages = 0;
    hr = pfl->GetFontCodePages(hdc, hfOrig, &dwFontCodePages);
    if (SUCCEEDED(hr)) {
      while (cch > 0) {
        DWORD dwActualCodePages;
        long cchActual;
        hr = pfl->GetStrCodePages(psz, cch, dwFontCodePages, &dwActualCodePages, &cchActual);
        if (FAILED(hr)) {
          break;
        }

        if (dwActualCodePages & dwFontCodePages) {
          TextOut(hdc, 0, 0, psz, cchActual);
        } else {
          HFONT hfLinked;
          if (FAILED(hr = pfl->MapFont(hdc, dwActualCodePages, 0, &hfLinked))) {
            break;
          }
          SelectFont(hdc, hfLinked);
          TextOut(hdc, 0, 0, psz, cchActual);
          SelectFont(hdc, hfOrig);
          pfl->ReleaseFont(hfLinked);
        }
        psz += cchActual;
        cch -= cchActual;
      }
      if (FAILED(hr)) {
        //  We started outputting characters so we have to finish.
        //  Do the rest without font linking since we have no choice.
        TextOut(hdc, 0, 0, psz, cch);
        hr = S_FALSE;
      }
    }

    pfl->Release();

    if (!(dwAlignOrig & TA_UPDATECP)) {
      SetTextAlign(hdc, dwAlignOrig);
      MoveToEx(hdc, ptOrig.x, ptOrig.y, NULL);
    }
  }

  return hr;
}


最后,我们将整个操作放进一个辅助函数:首先尝试font linking技术,如果失败,则用原来的方式绘制文本。

void TextOutTryFL(HDC hdc, int x, int y, LPCWSTR psz, int cch)
{
  if (FAILED(TextOutFL(hdc, x, y, psz, cch)) {
    TextOut(hdc, x, y, psz, cch);
  }
}

好了,我们完成了可靠的,带有font linking技术的TextOut了,我们回到原来的PaintContent函数来使用它。

void
PaintContent(HWND hwnd, PAINTSTRUCT *pps)
{
  TextOutTryFL(pps->hdc, 0, 0,
               TEXT("ABC\x0410\x0411\x0412\x0E01\x0E02\x0E03"), 9);
}


可以看到,现在字符串不再显示出那些黑色的方块了吧。
一个我没有优化的地方是我每次绘制文本时都创建了一个IMlangFontLink2指针。在一个“真正的程序”中,你可能在整个绘制上下文范围中创建一个multilanguage对象,重用它,避免每次绘制字符串都创建一次。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值