Graphics.MeasureString()が予想よりも高い数値を返すのはなぜですか?
-
05-07-2019 - |
質問
領収書を生成し、Graphicsオブジェクトを使用してDrawStringメソッドを呼び出し、必要なテキストを印刷しています。
graphics.DrawString(string, font, brush, widthOfPage / 2F, yPoint, stringformat);
これは、私がそれをするために必要なことに対してはうまく機能します。私は常に何を印刷するかを知っていたので、80mmのレシート用紙に適切に収まるように、文字列を手動でトリミングできました。次に、これをより柔軟にするための機能を少し追加する必要がありました。ユーザーは、下部に追加される文字列を渡すことができます。
何を入力するのかわからなかったため、ラップする文字数と文字列自体を取り込む独自のワードラップ関数を作成しました。文字数を調べるために、私はこのようなことをしていました:
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しか返されないのですか?
解決
WindowsClient.netから:
GDI +は、表示されるすべての文字列の両端に少量(1/6 em)を追加します。この1/6 emは、オーバーハング端(イタリック ' f 'など)のグリフを許可し、GDI +にグリッドフィッティング拡張を支援するためのわずかな余裕を与えます。
DrawString
のデフォルトアクションは、隣接する実行を表示する際に無効になります:
- まず、デフォルトのStringFormatは、各出力の各端に1/6 emを追加します。
- 第二に、グリッドに合わせた幅が設計よりも小さい場合、文字列はemまで収縮できます。
これらの問題を回避するには:
- 常に
MeasureString
およびDrawString
に、活版印刷のStringFormat(GenericTypographic
)に基づいたStringFormatを渡します。
グラフィックスTextRenderingHint
をTextRenderingHintAntiAlias
に設定します。このレンダリング方法では、アンチエイリアスとサブピクセルグリフの配置を使用して、グリッドフィッティングの必要性を回避しているため、本質的に解像度に依存しません。
.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 Frameworkを変更して GDI + のテキストレンダリングシステムの使用を停止し、 GDI を使用したいと考えていました。最初は、単に変更できることを望んでいました。
graphics.DrawString
GDI +の代わりに古い DrawText
APIを呼び出します。しかし、彼らはテキストの折り返しと間隔をGDI +のように正確に一致させることはできませんでした。そのため、GDI +を呼び出すために graphics.DrawString
を保持することを強制されました(互換性の理由。 graphics.DrawString
を呼び出していた人は、突然テキストが折り返されていないことに気付くでしょう)慣れている)。
GDIテキストレンダリングをラップするために、新しい静的 TextRenderer
クラスが作成されました。 2つのメソッドがあります:
TextRenderer.MeasureText
TextRenderer.DrawText
注:
TextRenderer
はGDIのラッパーですが、graphics.DrawString
は依然としてGDI +のラッパーです。
次に、既存のすべての.NETコントロールをどう処理するかという問題がありました。例:
-
ラベル
-
ボタン
-
TextBox
彼らは TextRenderer
(つまりGDI)を使用するように切り替えたいと考えていましたが、注意する必要がありました。 .NET 1.1で行ったように、コントロールの描画に依存している人がいるかもしれません。そして、「互換性のあるテキストレンダリング」が生まれました。
デフォルトでは、アプリケーションのコントロールは.NET 1.1と同じように動作します(これらは" 互換性"です)。
次の呼び出しで互換モードをオフにします:
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
他のヒント
Size = 11でFont '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/Width-of-text-in-italic-font
付録: 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);
}