Windows11 外字の問題(WIn32 API TextOutW)

平井 20 評価のポイント
2025-08-12T00:35:33.86+00:00

Windows11(バージョン24H2 ビルド26100.4652)とWindows10(バージョン22H2 ビルド19045.6036)で外字の表示(Win32 API TextOutWの描画)に違いがあります。

手順:

  1. 外字エディタで、MS明朝用にUnicode 0xE849(私用領域)の外字を登録する。
  2. テキストエディタ(おそらくWin32 APIを使用)に外字1文字と、外字以外の全角文字+外字を入力する。
  3. Windows11の場合、外字1文字では正常に外字が表示されるが、外字以外の全角を含む行では外字が別の文字に置換される。
  4. Windows10の場合、外字1文字と、外字以外の全角文字+外字、どちらも正常に外字が表示される。

Win32 APIではなく、Direct2Dでは問題が発生しないようです。

何か解決方法はありますでしょうか?

↓Windows10Windows10-22H2(Build19045.6036) Windows11↓Windows11-24H2(26100.4652)

開発者テクノロジ | C++
開発者テクノロジ | C++
C プログラミング言語の拡張機能として作成された高レベルの汎用プログラミング言語。低レベルのメモリ操作機能に加えて、オブジェクト指向、汎用、関数型の機能を備えています。
{count} 件の投票

質問作成者が受け入れた回答
  1. motosan 2,760 評価のポイント
    2025-08-15T12:29:36.0133333+00:00

    複雑になってしまいますが、私用領域とそれ以外に分けて DrawText で描画すればできるかもしれません。

    例です。

    ※ (修正)描画部分を関数にしてみました

    修正したはずが、反映されませんでした。

        case WM_PAINT: {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // 外字フォントファイルをロード
            const TCHAR* fontPath = L"C:\\Windows\\FONTS\\EUDC.TTE";
            if (AddFontResourceEx(fontPath, FR_PRIVATE, NULL) == 0) {
                MessageBox(hWnd, L"フォントのロードに失敗しました", L"エラー", MB_OK | MB_ICONERROR);
                return -1;
            }
            // EUDCフォントを作成
            HFONT hFont_EUDC = CreateFont(
                24, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
                DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
                DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"EUDC");
            // MS 明朝フォントを作成
            HFONT hFont_MSMincho = CreateFont(
                24, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
                SHIFTJIS_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
                DEFAULT_QUALITY, DEFAULT_PITCH, L"MS 明朝");
    
            // フォントを選択
            HFONT hOldFont = (HFONT)SelectObject(hdc, hFont_EUDC);
    
             // 以降を関数に変更しました。
            //// 描画領域
            //RECT rectMax = { 50, 200, 400, 250 };
            //RECT rect = rectMax;
            //// 描画するテキスト
            //const wchar_t text[]  = L"あいう\uE849かきく";
            //// 1回で描画するテキストの格納用
            //wchar_t wtext[1000];
            //memset(wtext, 0, sizeof(wtext));
            //const wchar_t* p = text;
            //const wchar_t* start_p = text;
            //int len_regular = 0;
            //int len_eudc = 0;
            //bool isEUDC = false;
            //while (true) {
            //    isEUDC = false;
            //    if (*p >= 0xE000 && *p <= 0xF8FF) {
            //        isEUDC = true;
            //    }
            //    if (*p == '\0' || (isEUDC && len_regular > 0) || (!isEUDC && len_eudc > 0)) {
            //        memset(wtext, 0, sizeof(wtext));
            //        if (len_regular > 0) {
            //            wcsncpy(wtext, start_p, len_regular);
            //            SelectObject(hdc, hFont_MSMincho);
            //            DrawText(hdc, wtext, -1, &rect, DT_LEFT | DT_CALCRECT);
            //            DrawText(hdc, wtext, -1, &rect, DT_LEFT);
            //            rect.left = rect.right;
            //        }
            //        if (len_eudc > 0) {
            //            wcsncpy(wtext, start_p, 1);
            //            SelectObject(hdc, hFont_EUDC);
            //            DrawText(hdc, wtext, -1, &rect, DT_LEFT | DT_CALCRECT);
            //            DrawText(hdc, wtext, -1, &rect, DT_LEFT);
            //            rect.left = rect.right;
            //        }
            //        start_p = p;
            //        len_eudc = 0;
            //        len_regular = 0;
            //    }
            //    if (*p == '\0') {
            //        break;
            //    }
            //    if (isEUDC) {
            //        len_eudc++;
            //    }
            //    else {
            //        len_regular++;
            //    }
            //    p++;
            //}
    
            // *** MyDrawText関数呼び出し
           RECT rect;
            rect = MyDrawText(hdc, hFont_EUDC, hFont_MSMincho,
                            50, 100,
                            L"あいう\uE849\uE849かきく\uE849");
            rect = MyDrawText(hdc, hFont_EUDC, hFont_MSMincho,
                            rect.left, rect.bottom + 10,
                            L"あいうえおかきくけこ");
    
            // フォントを元に戻す
            SelectObject(hdc, hOldFont);
            DeleteObject(hFont_EUDC);
            DeleteObject(hFont_MSMincho);
            ReleaseDC(hWnd, hdc);
            // 外字フォントファイルをメモリから削除
            RemoveFontResourceEx(fontPath, FR_PRIVATE, NULL);
    
            EndPaint(hWnd, &ps);
        } break;
    
    

    作成したMyDrawText 関数です。

    ※ DrawText で文字数指定できたので、mallocを使わないように変更しました。

    RECT MyDrawText(HDC p_hdc, HFONT p_hFont_EUDC, HFONT p_hFont,
                    int p_left, int p_top, const wchar_t* p_text) {
        // 全体の描画範囲(返り値)を格納
        RECT ret = { p_left, p_top, p_left, p_top };
        // DrawText DT_CALCRECTで計算された描画範囲を格納
        RECT rect = { p_left, p_top, p_left, p_top };
    
        // 私用領域とそれ以外を区別するための変数
        // 文字へのポインター
        const wchar_t* p = p_text;
        // 連続する領域の先頭のポインター
        const wchar_t* start_p = p_text;
        // start_p の文字列が私用領域の範囲か?
        bool isEUDC = false;
        // 描画する文字数
        int len = 0;
        // 文字が私用領域の範囲か?
        bool iswEUDC = false;
        while (true) {
            // 文字が私用領域の範囲かチェックする
            iswEUDC = false;
            if (*p >= 0xE000 && *p <= 0xF8FF) {
                // 私用領域の文字
                iswEUDC = true;
            }
            if (*p == '\0' || iswEUDC != isEUDC) {
                // フォントを選択する
                if (len > 0) {
                    if (isEUDC) {
                        SelectObject(p_hdc, p_hFont_EUDC);
                    }
                    else {
                        SelectObject(p_hdc, p_hFont);
                    }
                    // 描画範囲(ボックス)を計算する
                    DrawText(p_hdc, start_p, len, &rect, DT_LEFT | DT_CALCRECT);
                    // 描画する。
                    DrawText(p_hdc, start_p, len, &rect, DT_LEFT);
                    // 全体の描画範囲(ret)を計算する。(left, top は同じ)
                    ret.right = rect.right;
                    if (ret.bottom < rect.bottom) {
                        ret.bottom = rect.bottom;
                    }
    
                    // 次に描画する位置(rect.left)を計算する。
                    rect.left = rect.right;
                    // 次の領域の文字列用に初期化する
                    start_p = p;
                    isEUDC = iswEUDC;
                    len = 0;
                }
            }
            if (*p == '\0') {
                break;
            }
            len++;
            p++;
        }
        return ret;
    }
    

    実行結果を添付します。

    image

    みずらいですが、ご参考までに

    1 人がこの回答が役に立ったと思いました。

1 件の追加の回答

並べ替え方法: 最も役に立つ
  1. motosan 2,760 評価のポイント
    2025-08-13T14:23:42.5233333+00:00

    U+E849 のコードを問題なく使用することは難しいようです。

    WordなどはUnicodeの私用領域をフォントファイルから取り込まず、外字ファイルからだけ取り込むようにしているようです。

    一部のアプリは、フォントファイルに無い場合だけ外字ファイルから取り込むようにしているようです。(もう少し複雑かもしれません)

    下のような対応が考えられます。

    方法1)

      Unicodeで使える文字がないか調べて使う。(IMEパッドの手書き入力)

    方法2)

      私用領域の文字未登録のコードで外字を登録する。

    以上の方法は既存の文書の文字コードの書き換えが必要になります。

    ■ 表示できないアプリがあることを許容してU+E849 のコードを使う。

    方法3)

      質問にあるように Direct2D を使用するようにプログラムを修正する。

    方法4)

      外字ファイルのフォントで TextOutW で出力する。

      外字ファイルにないコードはシステムのデフォルトフォントが使用されるので、フォントを指定できない。

    描画部分のコード例(Copilotに聞きました)

        case WM_PAINT: {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // 外字フォントファイルをロード
            const TCHAR* fontPath = L"C:\\Windows\\FONTS\\EUDC.TTE";
            if (AddFontResourceEx(fontPath, FR_PRIVATE, NULL) == 0) {
                MessageBox(hWnd, L"フォントのロードに失敗しました", L"エラー", 
                           MB_OK | MB_ICONERROR);
                return -1;
            }
            // 描画するテキスト(外字を含む)
            TCHAR text[]  = L"あ\uE849";
            // EUDCフォントを作成
            HFONT hFont = CreateFont(
                24, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
                DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
                DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"EUDC");
            // フォントを選択
            HFONT hOldFont = (HFONT)SelectObject(hdc, hFont);
            // テキストを描画
            TextOut(hdc, 100, 50, text, ARRAYSIZE(text));
            // フォントを元に戻す
            SelectObject(hdc, hOldFont);
            DeleteObject(hFont);
            ReleaseDC(hWnd, hdc);
            // 外字フォントファイルをメモリから削除
            RemoveFontResourceEx(fontPath, FR_PRIVATE, NULL);
            EndPaint(hWnd, &ps);
        } break;
    

    ※ (参考).Net の FormattedText では下記のようにフォントと外字ファイルを同時に指定できます。

    new FontFamily("MS ゴシック, C:\\Windows\\Fonts\\EUDC.tte#EUDC"),
    

    外字についての資料が少ないので、とりあえず、考え付いたことです。

    ご参考になれば幸いです。

    2025/8/15 0:10

    人間 さんのコメントを考えていて私の勘違いに気づきました。

    Windows11 になって U+E849の外字が表示できなくなったのでどうすればU+E849の外字が正しく表示できるようになるかということだと思っていました。

    結局、Windows11のフォントでは TextOutW で MS明朝では正しく表示できない外字があるので、Direct2D 等の他の手法を考えるしかないと思います。

     同じ文字コードなのに文字の組み合わせによって表示が変わることがあるのは、明らかにバグですが、私用領域に最初から文字が登録されていることにより、顕在化したのだと思います。

    1 人がこの回答が役に立ったと思いました。

お客様の回答

質問作成者は回答に "承認済み"、モデレーターは "おすすめ" とマークできます。これにより、ユーザーは作成者の問題が回答によって解決したことを把握できます。