0

DrawStringテキストを異なるサイズのフォントで垂直方向に揃えるために使用したい。基本的に、ドルよりも小さなフォントでセントを印刷して、レポートに販売価格を印刷しようとしています。これはレポート エンジンにあり、Windows フォームではなく画像に書き込む必要があるため、使用するのDrawStringではなく使用する必要があると思いますTextRenderer

BaseLine にテキストを描画する方法を説明するさまざまな記事を見つけました。これらは問題なく動作しているようですが、サイズが異なる同じフォントが 1 ピクセルずつずれている可能性があります。私はこれらを取り、フォントのアセントデザイン情報を使用して整列する方法を考え出すことができると思っていましたが、うまくいきませんでした:-(

誰にも役立つサンプルコードはありますか?

更新 1 : 合計を変更して、垂直方向ではなく上揃えにしたいことを反映します。Arial 20 で印刷されたドル値の上部を、Arial 14 のセント値の上部と水平方向に揃えたいと思います。私が試したすべてのフォントに問題がありました。

次の画像では、Arial Black を使用しています。

上揃えのテキスト

大きいフォントでは、赤い線と 1 の上部の間にギャップがあることがわかりますが、小さいフォントに到達するまでにそれは消えます。

アリアル Arial では、ラインの上から始まります。

ヴェルダナ そして、これはVerdanaで、大きなフォントで大きなギャップから始まると思います.

これら 3 つについては、次のコードを使用しています。

float offset = myFont.SizeInPoints /
myFont.FontFamily.GetEmHeight(myFont.Style) *
(myFont.FontFamily.GetLineSpacing(myFont.Style) - myFont.FontFamily.GetCellAscent(myFont.Style));
float pixels = e.Graphics.DpiY / 72f * offset;
float baseline = pixels;// (int)(pixels + 0.5f);

PointF renderPt = new PointF(left, y - baseline);
e.Graphics.DrawString("$1", myFont, new SolidBrush(Color.Black), renderPt);

また、このサンプルをいくつかのテストの基礎として使用しました。アセント ラインの描画方法が正確であれば、最初の書き込みポイントを調整するだけでよいと考えました。残念ながら、フォント サイズを大きくしたり、別のフォントに変更したりすると、アセント ラインが不正確に描画されてしまい、そのパスをたどることができませんでした。

TextRendererWindowsフォームイベントを使用しておらOnPaintず、適切なグラフィックを取得する方法がわからなかったため、動作させる方法がわからなかったため、使用していません。少し時間に追われていましたが、それが次の選択肢になるかもしれません。

4

2 に答える 2

4

フォントは、歴史に基づいて物理的な世界から多くを受け継いできた領域です。残念ながら、それはコンピュータの動作と常に互換性があるとは限りません。

たとえば、物理的な世界のフォント サイズは、物理的な世界の同じフォント サイズと同じである必要はありません (タイプミスはありません)。これらのグリフを同じサイズで取得します。

Gヘルベチカ Gスクリプト

これらは同じサイズ (64 ポイント) ですが、同じサイズではありません。これは、書体の歴史的側面と関係があります。物理的な世界では、グリフは正方形の金属プレートに配置されていました。それはこれらのプレートのサイズであり、その上のグリフではありません - それらはプレート全体を埋めることができました。か否か。これは、コンピューターベースの書体にも当てはまります。グリフのバウンディング ボックス (= フォント サイズ) は同じですが、グリフ自体が異なることがわかります。

これは通常、タイポグラフィや印刷では問題にならず、これを補正するためにすばやく調整できます。

.net/GDI+ での描画では、このように特殊なケースでは別の問題です。ベースラインは「常に正確」です。つまり、ベースラインを使用すると、グリフの「下」(下降を含まない) の同じ配置が保証されます。上から揃える必要がある場合、問題が発生します。

これを (GDI+ で) 回避する 1 つの方法は、グリフ ビットマップを実際にスキャンして上部の開始点を探し、その結果を表すオフセットを使用してグリフを描画することです。BitmapLock を使用してスキャンし、バッファに直接アクセスします。

もちろん、 を使用Bitmap.GetPixelしてスキャンを実行することもできますが、大量のテキストがあると非常に処理が遅くなります。

アップデート:

ベアリングと呼ばれるものについて言及するのを忘れていました。この場合、アセントの上部からグリフの上部までのギャップを表すトップ サイド ベアリングです。残念ながら、これを GDI/GDI+ 経由で抽出することはできません (フォント パーサーを作成しないと)。ただし、WPF では、グリフからこの情報を抽出できます。

この図には示されていませんが、グリフのさまざまな部分を示しています。トップに相当するサイドベアリング:

ベアリング

詳細については、次のリンクを参照してください:
http://msdn.microsoft.com/en-us/library/system.windows.media.glyphtypeface.aspx

おそらく、このコードが役立つでしょう。私はこれを VB で書き、C# に翻訳しました (翻訳で何も失われないことを願っています)。これはグリフを受け取り、そのグリフ自体の正確なバウンディング ボックスを含むビットマップを返します。この方法では、結果のビットマップを必要な垂直位置に配置するだけです:

引数として WPF 書体が必要です (フォントは GDI+ ではなく WPF で開かれます)。サポートが必要な場合はお知らせください。

using System.Windows.Media;
using System.Windows.Media.Imaging;

static class WPFGlyphToGDIPBitmap
{
    public static System.Drawing.Bitmap GetBitmapOfChar(GlyphTypeface gt, _
                                                        char c, _
                                                        double ptSize, _
                                                        float dpi)
    {

        ushort ci = 0;
        if (!gt.CharacterToGlyphMap.TryGetValue(Strings.AscW(c), ci)) {
            if (!gt.CharacterToGlyphMap.TryGetValue(Strings.Asc(c), ci))
                    return null;
        }

        Geometry geo = gt.GetGlyphOutline(ci, ptSize, ptSize);
        GeometryDrawing gDrawing = new GeometryDrawing(System.Windows.Media.Brushes.Black, null, geo);
        DrawingImage geoImage = new DrawingImage(gDrawing);
        geoImage.Freeze();

        DrawingVisual viz = new DrawingVisual();
        DrawingContext dc = viz.RenderOpen;

        dc.DrawImage(geoImage, new Rect(0, 0, geoImage.Width, geoImage.Height));
        dc.Close();

        RenderTargetBitmap bmp = new RenderTargetBitmap(geoImage.Width, _
                                                        geoImage.Height, _
                                                        dpi, dpi, _
                                                        PixelFormats.Pbgra32);
        bmp.Render(viz);

        PngBitmapEncoder enc = new PngBitmapEncoder();
        enc.Frames.Add(BitmapFrame.Create(bmp));

        MemoryStream ms = new MemoryStream();
        enc.Save(ms);
        ms.Seek(0, SeekOrigin.Begin);

        enc = null;
        dc = null;
        viz = null;

        DisposeBitmap(bmp);

        System.Drawing.Bitmap gdiBMP = new System.Drawing.Bitmap(ms);
        ms.Dispose();

        //gdiBMP.Save("c:\test.png", System.Drawing.Imaging.ImageFormat.Png)

        return gdiBMP;

    }

}
public static void DisposeBitmap(RenderTargetBitmap bmp)
{
    if (bmp != null) {
        bmp.Clear();
    }
    bmp = null;
    GC.Collect();
    GC.WaitForPendingFinalizers();
}
于 2012-12-18T03:24:59.930 に答える
1
//-----------------------------------------------------------------------------------------------------------------
// MeasureLeading Function
// Measures the amount of white space above a line of text, in pixels. This is accomplished by drawing the text
// onto an offscreen bitmap and then looking at each row of pixels until a non-white pixel is found.
// The y coordinate of that pixel is the result. This represents the offset by which a line of text needs to be
// raised vertically in order to make it top-justified.
//-----------------------------------------------------------------------------------------------------------------

public static int MeasureLeading(string Text, Font Font) {

  Size sz = MeasureText(Text, Font);
  Bitmap offscreen = new Bitmap(sz.Width, sz.Height);
  Graphics ofg = Graphics.FromImage(offscreen);
  ofg.FillRectangle(new SolidBrush(Color.White), new Rectangle(0, 0, sz.Width, sz.Height));
  ofg.DrawString(Text, Font, new SolidBrush(Color.Black), 0, 0, StringFormat.GenericTypographic);

  for (int iy=0; iy<sz.Height; iy++) {
    for (int ix=0; ix<sz.Width; ix++) {
      Color c = offscreen.GetPixel(ix, iy);
      if ((c.R!=255) || (c.G!=255) || (c.B!=255)) return iy;
    }
  }

  return 0;
}

//-----------------------------------------------------------------------------------------------------------------
// MeasureText Method
// TextRenderer.MeasureText always adds about 1/2 em width of white space on the right,
// even when NoPadding is specified. But it returns zero for an empty string.
// To get the true string width, we measure the width of a string containing a single period
// and subtract that from the width of our original string plus a period.
//-----------------------------------------------------------------------------------------------------------------

public static System.Drawing.Size MeasureText(string Text, System.Drawing.Font Font) {
  System.Windows.Forms.TextFormatFlags flags
    = System.Windows.Forms.TextFormatFlags.Left
    | System.Windows.Forms.TextFormatFlags.Top
    | System.Windows.Forms.TextFormatFlags.NoPadding
    | System.Windows.Forms.TextFormatFlags.NoPrefix;
  System.Drawing.Size szProposed = new System.Drawing.Size(int.MaxValue, int.MaxValue);
  System.Drawing.Size sz1 = System.Windows.Forms.TextRenderer.MeasureText(".", Font, szProposed, flags);
  System.Drawing.Size sz2 = System.Windows.Forms.TextRenderer.MeasureText(Text + ".", Font, szProposed, flags);
  return new System.Drawing.Size(sz2.Width - sz1.Width, sz2.Height);
}
于 2015-04-27T14:10:53.463 に答える