none
gdi32.dllのGetTextExtentPoint32で取得できる幅が順番で異なる​ RRS feed

  • 質問

  • WPF、C#でgdi32.dllのGetTextExtentPoint32を使用して文字列の幅を取得して、上位側で改行位置を制御しています。
    LOGFONT構造体を作成し、CreateFontIndirectでフォントハンドルを取得して、デバイスコンテキストにフォントを設定しています。

    1. MSPゴシックで高さ34、幅0を指定して「えええええ」の値を取得すると、「155,34」が取得できます。
    2. MSPゴシックで高さ34、幅14を指定して「えええええ」の値を取得すると、「150,34」が取得できます。

    ただし、アプリケーション起動後に 1.⇒ 2.と実行した場合、2.が 1. と同じ値になってしまいます。
    逆に、アプリケーション起動後に 2.⇒ 1.と実行した場合、1.が 2. と同じ値になってしまいます。

    幅20で実行した場合には異なる値が取得できているため、APIの利用方法は正しいと考えられます。
    高さを35など変更した場合は現象が発生しません。
    文字列を「あああああ」とした場合は 1. 2.が同じ値となり、結果的に現象が確認できません。

    OS:Windows 10 Enterprise 1803
    開発環境:Visual Studio 2019(WPF、C#)
    LOGFONT fontInfo = new LOGFONT();
    fontInfo.lfHeight = 34;
    fontInfo.lfWidth = 0; //0と14で起動後に最初に利用した幅に依存する
    
    fontInfo.lfCharSet = (byte)FontCharSet.SHIFTJIS_CHARSET;
    fontInfo.lfClipPrecision = (byte)FontClipPrecision.CLIP_STROKE_PRECIS;
    fontInfo.lfEscapement = 0;
    fontInfo.lfFaceName = "MS P ゴシック";
    fontInfo.lfItalic = 0;
    fontInfo.lfOrientation = 0;
    fontInfo.lfOutPrecision = (byte) FontPrecision.OUT_STROKE_PRECIS;
    fontInfo.lfPitchAndFamily = (byte)(FontPitchAndFamily.VARIABLE_PITCH | FontPitchAndFamily.FF_ROMAN | FontPitchAndFamily.FF_SWISS);
    fontInfo.lfQuality = (byte)FontQuality.NONANTIALIASED_QUALITY;
    fontInfo.lfStrikeOut = 0;
    fontInfo.lfUnderline = 0;
    fontInfo.lfWeight = (int)FontWeight.FW_NORMAL;
    
    IntPtr hFont = CreateFontIndirect(fontInfo);
    
    //長さを取得
    IntPtr hDC1 = GetDC(IntPtr.Zero);
    IntPtr memDC = CreateCompatibleDC(hDC1);
    IntPtr hBitmap = CreateCompatibleBitmap(memDC, 1, 1);
    IntPtr oldBitmap = SelectObject(memDC, hBitmap);
    IntPtr oldFont = SelectObject(memDC, hFont);
    
    str1 = "えええええ"
    System.Drawing.Size drawSize = new System.Drawing.Size();
    GetTextExtentPoint32(memDC,str1,str1.Length, ref drawSize);
    
    SelectObject(memDC, oldFont);
    DeleteObject(hFont);
    SelectObject(memDC, oldBitmap);
    DeleteObject(hBitmap);
    DeleteObject(memDC);
    ReleaseDC(IntPtr.Zero,hDC1);



    2019年10月8日 1:10

すべての返信

  • 1. MSPゴシックで高さ34、幅0を指定して「えええええ」の値を取得すると、「155,34」が取得できます。
    2. MSPゴシックで高さ34、幅14を指定して「えええええ」の値を取得すると、「150,34」が取得できます。
    fontInfo.lfFaceName = "MS P ゴシック";

    "MSPゴシック" や "MS P ゴシック" は正確なフォント名ではないので、
    "MS Pゴシック" あるいは "MS PGothic" を指定してみるのはどうでしょうか。

    (試したわけではないので、同じ結果に終わるかもしれませんが)

    2019年10月8日 5:16
  • 0310developさん、こんにちは。フォーラムオペレーターのHarukaです。
    MSDNフォーラムにご投稿くださいましてありがとうございます。

    取得したテキストがどこから来たのか知りたいです。 それはコントロールまたは他の場所からですか。
    次に、問題を再現できる完成したコードを提供していただければ幸いです。

    どうぞよろしくお願いいたします。


    MSDN/ TechNet Community Support Haruka
    ~参考になった投稿には「回答としてマーク」をご設定ください。なかった場合は「回答としてマークされていない」も設定できます。同じ問題で後から参照した方が、情報を見つけやすくなりますので、 ご協力くださいますようお願いいたします。また、MSDNサポートに賛辞や苦情がある場合は、MSDNFSF@microsoft.comまでお気軽にお問い合わせください。~

    2019年10月17日 8:58
    モデレータ
  • 確認が遅くなり申し訳ございません。
    "MS Pゴシック"、"MS PGothic"、"MS P Gothic"と試してみましたが、
    結果として値に変化はありませんでした。
    "Meiryo"や"MS Gothic"とすると全く異なる値が返るためフォント指定はできていると推測しています。


    2019年10月18日 8:16
  • >取得したテキストがどこから来たのか知りたいです。 
    申し訳ありません。質問の意図がわからないのですが、テストアプリはソース上で文字列を指定しています。
    実際のアプリケーションはGUIで入力された値を使用しています。

    >問題を再現できる完成したコードを提供していただければ幸いです。
    DLLの参照部分を記載します。

            [DllImport("gdi32.dll")]
            private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiObj);
    
            [DllImport("gdi32.dll")]
            private static extern bool DeleteObject(IntPtr hObject);
    
            [DllImport("gdi32.dll", EntryPoint = "GetTextExtentPoint32W")]
            private static extern int GetTextExtentPoint32(IntPtr hdc, [MarshalAs(UnmanagedType.LPWStr)] string str, int len, ref System.Drawing.Size size);
    
            [DllImport("user32.dll")]
            public static extern IntPtr GetDC(IntPtr hWnd);
    
            [DllImport("gdi32.dll")]
            public static extern IntPtr CreateCompatibleDC([In] IntPtr hdc);
    
            [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap")]
            static extern IntPtr CreateCompatibleBitmap([In] IntPtr hdc, int nWidth, int nHeight);
    
            [DllImport("user32.dll")]
            static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);
    
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
            public class LOGFONT
            {
                public int lfHeight = 0;
                public int lfWidth = 0;
                public int lfEscapement = 0;
                public int lfOrientation = 0;
                public int lfWeight = 0;
                public byte lfItalic = 0;
                public byte lfUnderline = 0;
                public byte lfStrikeOut = 0;
                public byte lfCharSet = 0;
                public byte lfOutPrecision = 0;
                public byte lfClipPrecision = 0;
                public byte lfQuality = 0;
                public byte lfPitchAndFamily = 0;
                [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
                public string lfFaceName = string.Empty;
            }
            public enum FontWeight : int
            {
                FW_DONTCARE = 0,
                FW_THIN = 100,
                FW_EXTRALIGHT = 200,
                FW_LIGHT = 300,
                FW_NORMAL = 400,
                FW_MEDIUM = 500,
                FW_SEMIBOLD = 600,
                FW_BOLD = 700,
                FW_EXTRABOLD = 800,
                FW_HEAVY = 900,
            }
            public enum FontCharSet : byte
            {
                ANSI_CHARSET = 0,
                DEFAULT_CHARSET = 1,
                SYMBOL_CHARSET = 2,
                SHIFTJIS_CHARSET = 128,
                HANGEUL_CHARSET = 129,
                HANGUL_CHARSET = 129,
                GB2312_CHARSET = 134,
                CHINESEBIG5_CHARSET = 136,
                OEM_CHARSET = 255,
                JOHAB_CHARSET = 130,
                HEBREW_CHARSET = 177,
                ARABIC_CHARSET = 178,
                GREEK_CHARSET = 161,
                TURKISH_CHARSET = 162,
                VIETNAMESE_CHARSET = 163,
                THAI_CHARSET = 222,
                EASTEUROPE_CHARSET = 238,
                RUSSIAN_CHARSET = 204,
                MAC_CHARSET = 77,
                BALTIC_CHARSET = 186,
            }
            public enum FontPrecision : byte
            {
                OUT_DEFAULT_PRECIS = 0,
                OUT_STRING_PRECIS = 1,
                OUT_CHARACTER_PRECIS = 2,
                OUT_STROKE_PRECIS = 3,
                OUT_TT_PRECIS = 4,
                OUT_DEVICE_PRECIS = 5,
                OUT_RASTER_PRECIS = 6,
                OUT_TT_ONLY_PRECIS = 7,
                OUT_OUTLINE_PRECIS = 8,
                OUT_SCREEN_OUTLINE_PRECIS = 9,
                OUT_PS_ONLY_PRECIS = 10,
            }
            public enum FontClipPrecision : byte
            {
                CLIP_DEFAULT_PRECIS = 0,
                CLIP_CHARACTER_PRECIS = 1,
                CLIP_STROKE_PRECIS = 2,
                CLIP_MASK = 0xf,
                CLIP_LH_ANGLES = (1 << 4),
                CLIP_TT_ALWAYS = (2 << 4),
                CLIP_DFA_DISABLE = (4 << 4),
                CLIP_EMBEDDED = (8 << 4),
            }
            public enum FontQuality : byte
            {
                DEFAULT_QUALITY = 0,
                DRAFT_QUALITY = 1,
                PROOF_QUALITY = 2,
                NONANTIALIASED_QUALITY = 3,
                ANTIALIASED_QUALITY = 4,
                CLEARTYPE_QUALITY = 5,
                CLEARTYPE_NATURAL_QUALITY = 6,
            }
            [Flags]
            public enum FontPitchAndFamily : byte
            {
                DEFAULT_PITCH = 0,
                FIXED_PITCH = 1,
                VARIABLE_PITCH = 2,
                FF_DONTCARE = (0 << 4),
                FF_ROMAN = (1 << 4),
                FF_SWISS = (2 << 4),
                FF_MODERN = (3 << 4),
                FF_SCRIPT = (4 << 4),
                FF_DECORATIVE = (5 << 4),
            }
    
            [DllImport("gdi32.dll", CharSet = CharSet.Auto)]
            static extern IntPtr CreateFontIndirect([In, MarshalAs(UnmanagedType.LPStruct)] LOGFONT lplf);

    2019年10月18日 8:34
  • private と public が混在しているのが気になりましたが、コードを提示いただいたおかげで、こちらでも検証することができました。

    MS Pゴシックの Version 5.05 環境と Version 5.31 で、WPF および WinForms のプロジェクトで 32bit/64bit それぞれで試してみましたが、同様の事象が再現されました。

    また、CreateFontIndirect を CreateFont に変更してみた場合にも、同じ結果が検出されました。(適正値は「150,34」側であると想像しています)

    GetTextExtentPoint32 での測定結果が数ピクセルずれるという話は他にも事例があるようなので、代わりに DrawText + DT_CALCRECT を使ってみるのはどうでしょうか。

    測定方法が変わると都合が悪いのなら、GetTextExtentPoint32 を使う際に対処療法として、最初にダミーフォントを指定して計測してから、改めて本題の幅のフォントを指定しなおすようなコードに置き換えてみるとか。

    2019年10月21日 7:03
  • 魔界の仮面弁士様
    ご確認ありがとうございます。
    早速DrawText + DT_CALCRECTで確認しましたが、GetTextExtentPoint32 同様に最初に呼び出したサイズに依存してしまう現象になりました。
    1. MSPゴシックで高さ34、幅0を指定して「えええええ」の値を取得すると「0,0,62,34」
    2. MSPゴシックで高さ34、幅14を指定して「えええええ」の値を取得すると「0,0,60,34」

    やはり対処療法で検討するしかないかもしれませんね…

    2019年10月21日 8:36
  • 0310developさん、こんにちは。フォーラムオペレーターのHarukaです。
    コードをご共有いただきありがとうございます。

    問題を再現できたようです。 あなたの期待される結果が何であるかを知りたいです。
    同じ結果を取得しますか、それとも異なる結果を取得しますか。 それについて教えてください。

    また、ご提供したコードは正しいと思います。 
    そのため、得られる結果を変更したい場合は、コードデザインの担当者に助けを求める必要があると思います。

    どうぞよろしくお願いいたします。


    MSDN/ TechNet Community Support Haruka
    ~参考になった投稿には「回答としてマーク」をご設定ください。なかった場合は「回答としてマークされていない」も設定できます。同じ問題で後から参照した方が、情報を見つけやすくなりますので、 ご協力くださいますようお願いいたします。また、MSDNサポートに賛辞や苦情がある場合は、MSDNFSF@microsoft.comまでお気軽にお問い合わせください。~

    2019年10月22日 7:44
    モデレータ