{"id":38413,"date":"2004-07-16T06:58:00","date_gmt":"2004-07-16T06:58:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2004\/07\/16\/how-to-display-a-string-without-those-ugly-boxes\/"},"modified":"2004-07-16T06:58:00","modified_gmt":"2004-07-16T06:58:00","slug":"how-to-display-a-string-without-those-ugly-boxes","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20040716-00\/?p=38413","title":{"rendered":"How to display a string without those ugly boxes"},"content":{"rendered":"<p>\nYou&#8217;ve all seen those ugly boxes.  When you try to display a string\nand the font you have doesn&#8217;t support all of the characters in it,\nyou get an ugly box for the characters that aren&#8217;t available in the\nfont.\n<\/p>\n<p>\nStart with\n<a href=\"http:\/\/weblogs.asp.net\/oldnewthing\/archive\/2003\/07\/23\/54576.aspx\">\nour scratch program<\/a> and add this to the\n<code>PaintContent<\/code> function:\n<\/p>\n<pre>\nvoid\nPaintContent(HWND hwnd, PAINTSTRUCT *pps)\n{\n<font COLOR=\"blue\">    TextOutW(pps-&gt;hdc, 0, 0,\n            L\"ABC\\x0410\\x0411\\x0412\\x0E01\\x0E02\\x0E03\", 9);<\/font>\n}\n<\/pre>\n<p>\nThat string contains the first three letters from\nthree different alphabets: &#8220;ABC&#8221; from the Roman alphabet;\n&#8220;&#x410;&#x411;&#x412;&#8221;\nfrom the Cyrillic alphabet; and\n&#8220;&#xE01;&#xE02;&#xE03;&#8221; from the Thai alphabet.\n<\/p>\n<p>\nIf you run this program, you get a bunch of ugly boxes for the\nnon-Roman characters because the SYSTEM font is very limited in\nits character set support.\n<\/p>\n<p>\nBut how to pick the right font?  What if the string contained\nKorean or Japanese characters?  There is no single font that\ncontains every character defined by Unicode. (Or at least,\nnone that is commonly available.)  What do you do?\n<\/p>\n<p>\nThis is where\n<a HREF=\"http:\/\/msdn.microsoft.com\/workshop\/misc\/mlang\/tutorials\/fontlinking.asp\">\nfont linking<\/a> comes in.\n<\/p>\n<p>\nFont linking allows you to take a string and break it into\npieces, where each piece can be displayed in an appropriate font.\n<\/p>\n<p>\n<a HREF=\"http:\/\/msdn.microsoft.com\/workshop\/misc\/mlang\/reference\/ifaces\/imlangfontlink2\/imlangfontlink2.asp\">\nThe IMLangFontLink2 interface<\/a> provides the methods necessary\nto do this breaking.\n<a HREF=\"http:\/\/msdn.microsoft.com\/workshop\/misc\/mlang\/reference\/ifaces\/imlangcodepages\/getstrcodepages.asp\">\nGetStrCodePages<\/a> takes the string apart into chunks, such that\nall the characters in a chunk\ncan be displayed by the same font, and\n<a HREF=\"http:\/\/msdn.microsoft.com\/workshop\/misc\/mlang\/reference\/ifaces\/imlangfontlink\/mapfont.asp\">\nMapFont<\/a> creates the font.\n<\/p>\n<p>\nOkay, so let&#8217;s write our font-link-enabled version of the\nTextOut function.  We&#8217;ll do this in stages,\nstarting with the\n<a href=\"http:\/\/weblogs.asp.net\/oldnewthing\/archive\/2003\/11\/12\/55659.aspx\">idea kernel<\/a>.\n<\/p>\n<pre>\n#include &lt;mlang.h&gt;\nHRESULT TextOutFL(HDC hdc, int x, int y, LPCWSTR psz, int cch)\n{\n  ...\n  while (cch &gt; 0) {\n    DWORD dwActualCodePages;\n    long cchActual;\n    pfl-&gt;GetStrCodePages(psz, cch, 0, &amp;dwActualCodePages, &amp;cchActual);\n    HFONT hfLinked;\n    pfl-&gt;MapFont(hdc, dwActualCodePages, 0, &amp;hfLinked);\n    HFONT hfOrig = SelectFont(hdc, hfLinked);\n    TextOut(hdc, ?, ?, psz, cchActual);\n    SelectFont(hdc, hfOrig);\n    pfl-&gt;ReleaseFont(hfLinked);\n    psz += cchActual;\n    cch -= cchActual;\n  }\n  ...\n}\n<\/pre>\n<p>\nAfter figuring out which code pages the default font supports,\nwe walk through the string asking GetStrCodePages to give us\nthe next chunk of characters. From that chunk, we create a matching font\nand draw the characters in that font at &#8220;the right place&#8221;.\nRepeat until all the characters\nare done.\n<\/p>\n<p>\nThe rest is refinement and paperwork.\n<\/p>\n<p>\nFirst of all, what is &#8220;the right place&#8221;?\nWe want the next chunk to resume where the previous chunk left off.\nFor that, we take advantage of the TA_UPDATECP text alignment style,\nwhich says that GDI should draw the text at the current position,\nand update the current position to the end of the drawn text\n(therefore, in position for the next chunk).\n<\/p>\n<p>\nTherefore, part of the paperwork is to set the DC&#8217;s current position\nand set the text mode to TA_UPDATECP:<\/p>\n<pre>\n  SetTextAlign(hdc, GetTextAlign(hdc) | TA_UPDATECP);\n  MoveToEx(hdc, x, y, NULL);\n<\/pre>\n<p>\nThen we can just pass &#8220;0,0&#8221; as the coordinates to TextOut,\nbecause the coordinates passed to TextOut are ignored if\nthe text alignment mode is TA_UPDATECP;\nit always draws at the current position.\n<\/p>\n<p>\nOf course, we can&#8217;t just mess with the DC&#8217;s settings like this.\nIf the caller did not set TA_UPDATECP, then the caller\nis not expecting us to be meddling with the current\nposition.  Therefore, we have to save the original position\nand restore it (and the original text alignment mode) afterwards.\n<\/p>\n<pre>\n<font COLOR=\"blue\">  POINT ptOrig;\n  DWORD dwAlignOrig = GetTextAlign(hdc);\n  SetTextAlign(hdc, dwAlignOrig | TA_UPDATECP);\n  MoveToEx(hdc, x, y, &amp;ptOrig);<\/font>\n  while (cch &gt; 0) {\n    ...\n    TextOut(hdc, <font COLOR=\"blue\">0, 0<\/font>, psz, cchActual);\n    ...\n  }\n<font COLOR=\"blue\">  \/\/ if caller did not want CP updated, then restore it\n  \/\/ and restore the text alignment mode too\n  if (!(dwAlignOrig &amp; TA_UPDATECP)) {\n    SetTextAlign(hdc, dwAlignOrig);\n    MoveToEx(hdc, ptOrig.x, ptOrig.y, NULL);\n  }<\/font>\n<\/pre>\n<p>\nNext is a refinement:\nWe should take advantage of the second parameter to\nGetStrCodePages, which specifies the code pages we would prefer to use\nif a choice is avialable.\nClearly we should prefer to use the code pages supported by the font\nwe want to use, so that if the character can be displayed in that font\ndirectly, then we shouldn&#8217;t map an alternate font.\n<\/p>\n<pre>\n<font COLOR=\"blue\">  HFONT hfOrig = (HFONT)GetCurrentObject(hdc, OBJ_FONT);\n  DWORD dwFontCodePages = 0;\n  pfl-&gt;GetFontCodePages(hdc, hfOrig, &amp;dwFontCodePages);<\/font>\n  ...\n  while (cch &gt; 0) {\n    pfl-&gt;GetStrCodePages(psz, cch, <font COLOR=\"blue\">dwFontCodePages<\/font>, &amp;dwActualCodePages, &amp;cchActual);\n    <font COLOR=\"blue\">if (dwActualCodePages &amp; dwFontCodePages) {\n      \/\/ our font can handle it - draw directly using our font\n      TextOut(hdc, 0, 0, psz, cchActual);\n    } else {<\/font>\n      ... MapFont etc ...\n    <font COLOR=\"blue\">}<\/font>\n  }\n  ...\n<\/pre>\n<p>\nOf course, you probably wonder this magical <code>pfl<\/code> comes\nfrom.  It comes from\n<a HREF=\"http:\/\/msdn.microsoft.com\/workshop\/misc\/mlang\/reference\/objects\/cmultilanguage.asp\">\nthe Multilanguage Object<\/a> in mlang.<\/p>\n<pre>\n<font COLOR=\"blue\">  IMLangFontLink2 *pfl;\n  CoCreateInstance(CLSID_CMultiLanguage, NULL,\n                   CLSCTX_ALL, IID_IMLangFontLink2, (void**)&amp;pfl);<\/font>\n  ...\n<font COLOR=\"blue\">  pfl-&gt;Release();<\/font>\n<\/pre>\n<p>\nAnd of course, all the errors we&#8217;ve been ignoring need to be taken care of.\nThis does create a big of a problem if we run into an error after we have\nalready made it through a few chunks.  What should we do?\n<\/p>\n<p>\nI&#8217;m going to handle the error by drawing the string in the original font,\nugly boxes and all.  We can&#8217;t erase the characters we already drew,\nand we can&#8217;t just draw half of the string (for our caller won&#8217;t know where\nto resume).\nSo we just draw with the original font and hope for the best.\nAt least it&#8217;s no worse than it was before font linking.\n<\/p>\n<p>\nPut all of these refinements together and you get this final function:\n<\/p>\n<pre>\nHRESULT TextOutFL(HDC hdc, int x, int y, LPCWSTR psz, int cch)\n{\n  HRESULT hr;\n  IMLangFontLink2 *pfl;\n  if (SUCCEEDED(hr = CoCreateInstance(CLSID_CMultiLanguage, NULL,\n                      CLSCTX_ALL, IID_IMLangFontLink2, (void**)&amp;pfl))) {\n    HFONT hfOrig = (HFONT)GetCurrentObject(hdc, OBJ_FONT);\n    POINT ptOrig;\n    DWORD dwAlignOrig = GetTextAlign(hdc);\n    if (!(dwAlignOrig &amp; TA_UPDATECP)) {\n      SetTextAlign(hdc, dwAlignOrig | TA_UPDATECP);\n    }\n    MoveToEx(hdc, x, y, &amp;ptOrig);\n    DWORD dwFontCodePages = 0;\n    hr = pfl-&gt;GetFontCodePages(hdc, hfOrig, &amp;dwFontCodePages);\n    if (SUCCEEDED(hr)) {\n      while (cch &gt; 0) {\n        DWORD dwActualCodePages;\n        long cchActual;\n        hr = pfl-&gt;GetStrCodePages(psz, cch, dwFontCodePages, &amp;dwActualCodePages, &amp;cchActual);\n        if (FAILED(hr)) {\n          break;\n        }\n        if (dwActualCodePages &amp; dwFontCodePages) {\n          TextOut(hdc, 0, 0, psz, cchActual);\n        } else {\n          HFONT hfLinked;\n          if (FAILED(hr = pfl-&gt;MapFont(hdc, dwActualCodePages, 0, &amp;hfLinked))) {\n            break;\n          }\n          SelectFont(hdc, hfLinked);\n          TextOut(hdc, 0, 0, psz, cchActual);\n          SelectFont(hdc, hfOrig);\n          pfl-&gt;ReleaseFont(hfLinked);\n        }\n        psz += cchActual;\n        cch -= cchActual;\n      }\n      if (FAILED(hr)) {\n        \/\/  We started outputting characters so we have to finish.\n        \/\/  Do the rest without font linking since we have no choice.\n        TextOut(hdc, 0, 0, psz, cch);\n        hr = S_FALSE;\n      }\n    }\n    pfl-&gt;Release();\n    if (!(dwAlignOrig &amp; TA_UPDATECP)) {\n      SetTextAlign(hdc, dwAlignOrig);\n      MoveToEx(hdc, ptOrig.x, ptOrig.y, NULL);\n    }\n  }\n  return hr;\n}\n<\/pre>\n<p>\nFinally, we can wrap the entire operation inside a helper function\nthat first tries with font linking and if that fails, then just draws\nthe text the old-fashioned way.\n<\/p>\n<pre>\nvoid TextOutTryFL(HDC hdc, int x, int y, LPCWSTR psz, int cch)\n{\n  if (FAILED(TextOutFL(hdc, x, y, psz, cch)) {\n    TextOut(hdc, x, y, psz, cch);\n  }\n}\n<\/pre>\n<p>\nOkay, now that we have our font-linked TextOut with fallback,\nwe can go ahead and adjust our <code>PaintContent<\/code> function to use it.\n<\/p>\n<pre>\nvoid\nPaintContent(HWND hwnd, PAINTSTRUCT *pps)\n{\n  TextOutTryFL(pps-&gt;hdc, 0, 0,\n               TEXT(\"ABC\\x0410\\x0411\\x0412\\x0E01\\x0E02\\x0E03\"), 9);\n}\n<\/pre>\n<p>\nObserve that the string is now displayed with no black boxes.\n<\/p>\n<p>\nOne refinement I did not do was to avoid creating the\nIMlangFontLink2 pointer each time we want to draw text.\nIn a &#8220;real program&#8221; you would probably create the multilanguage\nobject once per drawing context (per window, perhaps) and re-use it\nto avoid going through the whole object creation codepath each time\nyou want to draw a string.\n<\/p>\n<p>\n[Raymond is currently on vacation; this message was pre-recorded.]<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You&#8217;ve all seen those ugly boxes. When you try to display a string and the font you have doesn&#8217;t support all of the characters in it, you get an ugly box for the characters that aren&#8217;t available in the font. Start with our scratch program and add this to the PaintContent function: void PaintContent(HWND hwnd, [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-38413","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You&#8217;ve all seen those ugly boxes. When you try to display a string and the font you have doesn&#8217;t support all of the characters in it, you get an ugly box for the characters that aren&#8217;t available in the font. Start with our scratch program and add this to the PaintContent function: void PaintContent(HWND hwnd, [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/38413","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=38413"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/38413\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=38413"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=38413"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=38413"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}