C# - FontをPixel単位にしたいときはGraphicsUnit.Pixelを指定しよう（兼 フォントメトリクス再調査）

以前投稿した [C# - フォントメトリクスを調査してみたらカオスだった件 - 未解決](https://qiita.com/kob58im/items/086b39b48caf091f16c8) のイケてなかったとこを修正してみた。

# イケてなかったとこ#1 - 単位が合っていなかった

フォントのインスタンスを生成するときに、
`new Font(フォント名,フォントサイズ)`
で指定していたが、このコンストラクタでは`フォントサイズ`は<b>ピクセル単位ではなくポイント単位</b>であり、解像度に依存する。
ピクセル単位で作りたいときは、
`new Font(フォント名,フォントサイズ,GraphicsUnit.Pixel)`
のように指定する必要がある。



# イケてなかったとこ#2 - フォントが代用されていた

代用されるのを避けるには、
`StringFormat`のプロパティ`FormatFlags`に`StringFormatFlags.NoFontFallback;`
を指定する必要がある。
（前記事でコメント頂いて知りました。感謝！）

# 画面キャプチャ

accent と accent+descent の位置に補助線（それぞれ青の点線と黒の実線）を入れています。

accent+descentで高さ方向が収まるっぽい。
[公式](https://docs.microsoft.com/ja-jp/dotnet/framework/winforms/advanced/how-to-obtain-font-metrics)には下記の記載があるので、多分あってるはず。
> Note that the em height (also called size or em size) is not the sum of the ascent and the descent. <b>The sum of the ascent and the descent is called the cell height.</b> The cell height minus the internal leading is equal to the em height. The cell height plus the external leading is equal to the line spacing.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/59a5b6d8-e1eb-68fd-17d5-fa4a4205ffd6.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/a408ecc4-88ff-0bc2-f17d-6a954cf2f8ed.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/e92fe365-3040-1536-58df-a4d0257e94a7.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/ff0739ed-cd3c-9c49-5826-2ed90c871664.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/45b6929d-c0dc-3110-97c2-c3cafbd5ce16.png)

# ソースコード

```csharp

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Windows.Forms;

class FontMetricsTest : Form
{
    TrackBar   trackbar;
    PictureBox pct;
    ComboBox   cmbFonts;
    TextBox    txtContent;

    FontMetricsTest()
    {
        Text = "Font metrics test";
        ClientSize = new Size(600, 400);

        cmbFonts = new ComboBox();
        foreach (FontFamily ff in FontFamily.Families) {
            cmbFonts.Items.Add(ff.Name);
        }
        cmbFonts.Text = SystemFonts.DefaultFont.Name;
        cmbFonts.Location = new Point(0,0);
        cmbFonts.Width = 300;
        cmbFonts.DropDownHeight = 500;
        cmbFonts.DropDownStyle = ComboBoxStyle.DropDownList;
        cmbFonts.SelectedIndexChanged += (sender,e)=>{MyRedraw();};
        Controls.Add(cmbFonts);

        trackbar = new TrackBar();
        trackbar.Location = new Point(300,0);
        trackbar.Maximum = 100;
        trackbar.Value   = 50;
        trackbar.Minimum = 1;
        trackbar.TickFrequency = 33;
        trackbar.ValueChanged += (sender,e)=>{MyRedraw();};
        Controls.Add(trackbar);

        txtContent = new TextBox();
        txtContent.Location = new Point(0, 50);
        txtContent.Width = 300;
        txtContent.Text = "g あいう Qiita";
        txtContent.TextChanged += (sender,e)=>{MyRedraw();};
        Controls.Add(txtContent);

        pct = new PictureBox();
        pct.Location = new Point(0, 80);
        pct.Size = new Size(600, 300);
        pct.Image = new Bitmap(600,300);
        Controls.Add(pct);

        MyRedraw();
    }

    void MyRedraw()
    {
        int w = pct.Image.Width;
        int h = pct.Image.Height;

        string fontName = cmbFonts.Text;
        Font font = new Font(fontName, (float)trackbar.Value, GraphicsUnit.Pixel);
        
        string text = txtContent.Text;
        if (font.Name != fontName) {
            text = "Font unmatch: \"" + font.Name + "\" is loaded.";
        }

        using ( Graphics g = Graphics.FromImage(pct.Image) ) {
            g.Clear(Color.White);
            
            FontFamily ff = font.FontFamily;

            int ascent = ff.GetCellAscent(font.Style);
            int descent = ff.GetCellDescent(font.Style);
            int emHeight = ff.GetEmHeight(font.Style);
            
            float fontHeight = font.GetHeight(g);
            float ascentSize    = font.Size  * ascent  / emHeight;
            float descentSize   = font.Size  * descent  / emHeight;

            Console.Write("==== "); Console.Write(font.Name); Console.WriteLine(" ====");
            Console.Write("font.Size: ");  Console.WriteLine(font.Size);
            Console.Write("fontHeight: "); Console.WriteLine(fontHeight);
            Console.Write("sum in pixel: ");        Console.WriteLine(ascentSize+descentSize);
            Console.Write("ascent in pixel: ");     Console.WriteLine(ascentSize);
            Console.Write("descent in pixel: ");    Console.WriteLine(descentSize);
            Console.Write("sum: ");        Console.WriteLine(ascent+descent);
            Console.Write("ascent: ");     Console.WriteLine(ascent);
            Console.Write("descent: ");    Console.WriteLine(descent);
            Console.Write("emHeight: ");   Console.WriteLine(emHeight);
            
            Pen pen = new Pen(Color.Black, 1.0f);
            Pen penDash = new Pen(Color.Blue, 1.0f);
            penDash.DashStyle = DashStyle.Dash;
            g.DrawLine(pen,     0, ascentSize+descentSize, w, ascentSize+descentSize);
            g.DrawLine(penDash, 0, ascentSize, w, ascentSize);

            StringFormat sf = new StringFormat();
            sf.FormatFlags = StringFormatFlags.NoFontFallback;
            g.DrawString(text, font, Brushes.Black, new PointF(0,0), sf);
        }

        pct.Refresh();
    }

    [STAThread]
    static void Main(string[] args)
    {
        Application.Run(new FontMetricsTest());
    }
}
```