web-dev-qa-db-ja.com

Graphics.MeasureString()が予想よりも多い数を返すのはなぜですか?

領収書を生成しています。Graphicsオブジェクトを使用してDrawStringメソッドを呼び出し、必要なテキストを出力しています。

graphics.DrawString(string, font, brush, widthOfPage / 2F, yPoint, stringformat);

これは、私がそれを実行するために必要なことに対してうまく機能しました。何を印刷するのかは常にわかっていたので、80mmのレシート用紙に適切に収まるように、手作業で文字列をトリミングできました。次に、これをより柔軟にする機能を少し追加する必要がありました。ユーザーは、下部に追加される文字列を渡すことができます。

彼らが何を置くのかわからなかったので、ラップする文字と文字列自体を取り込む独自のWordラップ関数を作成しました。文字数を調べるために、私は次のようなことをしていました:

float width = document.DefaultPageSettings.PrintableArea.Width;
int max = (int)(width / graphics.MeasureString("a", font).Width);

これで、幅は283を返します。これはmmで約72です。これは、80mm用紙のマージンを考慮する場合に意味があります。

しかし、MeasureStringメソッドはCourier New 8ptフォントで10.5を返します。したがって、私が36〜40であると予想していたものを回避する代わりに、26を取得し、その結果、2行のテキストが3〜4に変わります。

PrintableArea.Widthの単位は1/100インチであり、グラフィックスオブジェクトのPageUnitはDisplayです(これは通常、プリンターでは1/100インチです)。では、なぜ26しか返されないのですか?

45
Brandon

WindowsClient.netから:

GDI +は、表示されるすべての文字列の両端に少量(1/6 em)を追加します。この1/6 emは、端がオーバーハングしているグリフ(イタリック 'f'など)を許可し、グリッドフィッティングの拡張に役立つ少しの余裕をGDI +に与えます。

DrawStringのデフォルトアクションは、隣接する実行を表示する際に無効になります。

  • まず、デフォルトのStringFormatは、各出力の両端に1/6 emを追加します。
  • 次に、グリッドにフィットした幅が設計よりも小さい場合、文字列は最大emまで縮小できます。

これらの問題を回避するには:

  • 文字列のStringFormat(MeasureString)に基づいて、常にDrawStringおよびGenericTypographicにStringFormatを渡します。
    グラフィックスTextRenderingHintTextRenderingHintAntiAliasに設定します。このレンダリング方法は、アンチエイリアスとサブピクセルグリフ配置を使用してグリッドフィッティングの必要性を回避するため、本質的に解像度に依存しません。

.NETでテキストを描画する方法は2つあります。

  • GDI +(graphics.MeasureStringおよびgraphics.DrawString
  • GDI(TextRenderer.MeasureTextおよびTextRenderer.DrawText

Michael Kaplanの(rip)優れたブログ Sorting It All Out 、.NET 1.1では、すべてがテキストレンダリングにGDI +を使用しました。しかし、いくつかの問題がありました:

  • GDI +のややステートレスな性質によって引き起こされるパフォーマンスの問題がいくつかあります。デバイスコンテキストが設定され、各呼び出しの後に元の状態が復元されます。
  • 国際テキストの整形エンジンは、Windows/UniscribeおよびAvalon(Windows Presentation Foundation)で何度も更新されていますが、GDI +では更新されていないため、新しい言語の国際レンダリングサポートの品質は同じではありません。

したがって、彼らは.NETフレームワークを変更してGDI +のテキストレンダリングシステムを停止し、を使用することを望んでいることを知っていました[〜#〜] gdi [〜#〜]。最初、彼らは単に変更できることを望んでいました:

graphics.DrawString

gDI +の代わりに古いDrawText AP​​Iを呼び出す。しかし、テキストの折り返しと間隔をGDI +のように正確に一致させることはできませんでした。そのため、GDI +を呼び出すためにgraphics.DrawStringを維持することを余儀なくされました(互換性の理由。graphics.DrawStringを呼び出していた人々は、テキストが以前のように折り返されていないことに突然気づきました)。

GDIテキストレンダリングをラップするために、新しい静的TextRendererクラスが作成されました。これには2つのメソッドがあります:

TextRenderer.MeasureText
TextRenderer.DrawText

注:TextRendererはGDIのラッパーですが、graphics.DrawStringはGDI +のラッパーです。


次に、既存のすべての.NETコントロールをどうするかという問題がありました。

  • Label
  • Button
  • TextBox

彼らはTextRenderer(つまりGDI)を使用するように切り替えたいと思っていましたが、注意が必要でした。 .NET 1.1の場合のように、コントロールの描画に依存していた人もいるかもしれません。そして、「互換性のあるテキストレンダリング」が誕生しました。

デフォルトでは、アプリケーションのコントロールは.NET 1.1の場合と同様に動作します(「compatible」です)。

以下を呼び出すことにより、互換モードをオフにします

Application.SetCompatibleTextRenderingDefault(false);

これにより、アプリケーションの国際化サポートが向上し、より速く、より速くなります。総括する:

SetCompatibleTextRenderingDefault(true)  SetCompatibleTextRenderingDefault(false)
=======================================  ========================================
 default                                  opt-in
 bad                                      good
 the one we don't want to use             the one we want to use
 uses GDI+ for text rendering             uses GDI for text rendering
 graphics.MeasureString                   TextRenderer.MeasureText
 graphics.DrawString                      TextRenderer.DrawText
 Behaves same as 1.1                      Behaves *similar* to 1.1
                                          Looks better
                                          Localizes better
                                          Faster

GDIフォント描画に使用されるGDI + TextRenderingHintと対応する LOGFONT Quality の間のマッピングに注意することも役立ちます:

TextRenderingHint           mapped by TextRenderer to LOGFONT quality
========================    =========================================================
ClearTypeGridFit            CLEARTYPE_QUALITY (5) (Windows XP: CLEARTYPE_NATURAL (6))
AntiAliasGridFit            ANTIALIASED_QUALITY (4)
AntiAlias                   ANTIALIASED_QUALITY (4)
SingleBitPerPixelGridFit    PROOF_QUALITY (2)
SingleBitPerPixel           DRAFT_QUALITY (1)
else (e.g.SystemDefault)    DEFAULT_QUALITY (0)

サンプル

以下は、GDI +(graphics.DrawString)とGDI(TextRenderer.DrawText)テキストのレンダリング)の比較です。

GDI +TextRenderingHintClearTypeGridFit[〜#〜] gdi [〜#〜]CLEARTYPE_QUALITY

enter image description here

GDI +TextRenderingHintAntiAlias[〜#〜] gdi [〜#〜]ANTIALIASED_QUALITY

enter image description here

GDI +TextRenderingHintAntiAliasGridFit[〜#〜] gdi [〜#〜]サポートされていません、ANTIALIASED_QUALITYを使用します:

enter image description here

GDI +TextRenderingHintSingleBitPerPixelGridFit[〜#〜] gdi [〜#〜]PROOF_QUALITY

enter image description here

GDI +TextRenderingHintSingleBitPerPixel[〜#〜] gdi [〜#〜]DRAFT_QUALITY

enter image description here

DRAFT_QUALITYPROOF_QUALITYと同一であるのは奇妙です。これはCLEARTYPE_QUALITYと同一です。

も参照してください

148
Ian Boyd

Courier New Size 11

サイズ= 11のフォント「Courier New」を作成すると、上の画像のような出力が得られます。高さが下線を含まない14ピクセルであることがわかります。幅は正確に14ピクセルです(各文字に7ピクセル)。

したがって、このフォントは14x14ピクセルをレンダリングします。

ただし、TextRenderer.MeasureText()は代わりに21ピクセルの幅を返します。正確な値が必要な場合、これは役に立ちません。

解決策は次のコードです。

Font i_Courier = new Font("Courier New", 11, GraphicsUnit.Pixel);

Win32.SIZE k_Size;
using (Bitmap i_Bmp = new Bitmap(200, 200, PixelFormat.Format24bppRgb))
{
    using (Graphics i_Graph = Graphics.FromImage(i_Bmp))
    {
        IntPtr h_DC = i_Graph.GetHdc();
        IntPtr h_OldFont = Win32.SelectObject(h_DC, i_Courier.ToHfont());

        Win32.GetTextExtentPoint32(h_DC, "Áp", 2, out k_Size);

        Win32.SelectObject(h_DC, h_OldFont);
        i_Graph.ReleaseHdc();
    }
}

k_Sizeには正しいサイズが含まれます:14x14

重要:このコードは、通常のフォントを正しく測定します。イタリックフォント(常に右側にオーバーハングがある)にも正確な値が必要な場合は、この記事に記載されているリンクをお読みください: http://www.codeproject.com/Articles/14915/イタリックフォントのテキストの幅

付録:C#でAPI呼び出しを使用したことがない人のために、Win32クラスの作成方法のヒントを示します。これは完全ではありません。詳細は http://www.pinvoke.net をご覧ください。

using System.Runtime.InteropServices;

public class Win32
{       
    [StructLayout(LayoutKind.Sequential)]
    public struct SIZE
    {
        public int cx;
        public int cy;
    }

    [DllImport("Gdi32.dll")]
    public static extern bool GetTextExtentPoint32(IntPtr hdc, string lpString, int cbString, out SIZE lpSize);

    [DllImport("Gdi32.dll")]
    public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
}
7
Elmue

これがどのように機能するかを理解するのに役立つ説明です。そして、各文字の前後に多かれ少なかれスペースの原因は何ですか。

GDI DrawString Configurator App

画面キャプチャ

0
Cheva