.NET GraphicsPath.AddStringで縁取り文字を描きたい

GraphicsPathクラスで文字のパスを追加して、縁取りを追加してから文字を描きたい。

```csharp
// ※using、Dispose文は省略
var bitmap = new Bitmap(1280, 200);
// 透過
bitmap.MakeTransparent();
// BitmapからGraphicsを生成
var graphics = Graphics.FromImage(bitmap);

var stringFormat = new StringFormat();
// どんなに長くて単語の区切りが良くても改行しない
stringFormat.FormatFlags = StringFormatFlags.NoWrap;
// どんなに長くてもトリミングしない
stringFormat.Trimming = StringTrimming.None;
// ハイクオリティレンダリング
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
// アンチエイリアスをかける
graphics.SmoothingMode = SmoothingMode.HighQuality;

// GraphicsPathを生成
var gp = new GraphicsPath();

// パスに文字を追加
gp.AddString("山路を登りながら、智に働けば角が立つ。", new FontFamily("メイリオ"), 0, 46, new Point(8, 8), stringFormat);

// 縁取りをする。
graphics.DrawPath(new Pen(Color.Black, 16), gp);
// 文字を塗りつぶす。
graphics.FillPath(new SolidBrush(Color.White), gp);

// テスト用にBitmapの内容をD:\result.pngに出力
bitmap.Save("D:\\result.png", System.Drawing.Imaging.ImageFormat.Png);
```

注意して欲しいのが、縁取りする場合の文字の座標。**文字のパスを追加した座標を基準に、中央から太さが変わっていくので文字の座標をを縁取りのサイズの半分ずらす必要がある**。上のサンプルでは、縁取りのサイズが16なので文字自体を``new Point(8, 8)``としている。

# レンダリング結果

![result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498509/682c4adc-42e1-1085-d0ef-e9410d53c4fb.png)

「智に働けば"角が"立つ」、ってね！

**やかましいわ！　うおー！　なんかおかしい！**

文字自体のパスは正常に追加されてるが、``GraphicsPath.DrawPath``による縁取りの描画が破綻しているっぽい。

フォントによって結果が異なりますが(ハミングとかスーラとか、角丸系のフォントは大丈夫)、角張ってるフォントはこうなりやすい……。

# PenクラスのLineJoinプロパティとMiterLimitプロパティ

https://docs.microsoft.com/ja-jp/dotnet/api/system.drawing.pen

マイクロソフトのドキュメントの登場だー！　Penクラスのプロパティ弄れば解決できるかな、と思いとりあえずこのページを観てみる。すると「LineJoin - この Pen で描画された連続する 2 本の直線の終点の接合スタイルを取得または設定します。」といういかにもそれっぽいプロパティを見つけたのでこの列挙型を変更してみる。

## LineJoin.Bevel

```csharp
graphics.DrawPath(new Pen(Color.Black, 16) { LineJoin = LineJoin.Bevel }, gp);
```

![result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498509/9b59505c-3b42-5535-c868-9fda11e8ccf0.png)

斜めの縁取りが生成される感じ。3DCGのベベルエッジと同じ感じですね。

## LineJoin.Miter

```csharp
graphics.DrawPath(new Pen(Color.Black, 16) { LineJoin = LineJoin.Miter }, gp);
```

![result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498509/cbb851d7-c435-6875-9fbd-7ce4eced56e4.png)

``LineJoin.Miter = 0``なのでこれがデフォルト。

## LineJoin.MiterClipped

```csharp
graphics.DrawPath(new Pen(Color.Black, 16) { LineJoin = LineJoin.MiterClipped }, gp);
```

![result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498509/4a4c4c81-3f6a-432d-b5bf-5dc7d4322c8b.png)

これも``LineJoin.Miter``とほとんど同じなんだけど、``MiterLimit``の値を上回ったときの処理方法が違うっぽい。

```csharp
graphics.DrawPath(new Pen(Color.Black, 16) { LineJoin = LineJoin.Miter, MiterLimit = 2 }, gp);
graphics.DrawPath(new Pen(Color.Black, 16) { LineJoin = LineJoin.MiterClipped, MiterLimit = 2 }, gp);
```

![result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498509/8704dc72-3776-e6d6-a957-5876914f7016.png)

違うはず……なんだけど違いがよく分かんねぇわ。フォトショで減算したらたしかに違うって事はわかるけど。

## LineJoin.Round

```csharp
graphics.DrawPath(new Pen(Color.Black, 16) { LineJoin = LineJoin.Round} , gp);
```

![result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498509/7d011e66-2799-8b5a-e8f9-9baf86d1169a.png)

**個人的に1番好き**。角張っていないので他のと比べて優しい感じ。

# まとめ

* カクカクした縁取りを行うなら、``LineJoin.Miter``を指定して適切な``MiterLimit``を指定する。
* 丸い縁取りを行うなら、``LineJoin.Round``を指定する。