PDFSharpでフォントを指定する

## 初めに
C#でPDFを作成する際にPDFSharpを使用したのですが、フォントの指定で色々ありましたので記録しておきます。

## 実行環境
実行環境は以下の通りです。
* .NET 8.0 - Windows
* PDFSharp-WPF 6.1.1

PDFSharpは1.50で数年止まっていたのですが、2023年11月に.NET環境に対応した6.0.0が出て開発が再開されたようです。NuGetでインストールしましょう。

今回はPDFSharp(core)ではなくPDFSharp-WPFを使用します。
coreの方は自分でFontResolverを書く必要があります。
PDFSharp-WPFの方はWindows専用になりますがデフォルトのリゾルバでインストールされたフォントを参照することができます。
PDFSharp-GDIもWindows専用ですがPDFSharp-WPFよりも古い仕組みなのでおそらく本記事の後半の方法は使えないと思われます。

## コード例
コード例を見た方が早いですね。
```csharp
// リゾルバを自分で書く必要はない
//GlobalFontSettings.FontResolver = new MyFontResolver();

var document = new PdfDocument();
var page = document.AddPage();
page.Size = PageSize.A4;
page.Orientation = PageOrientation.Landscape;
XGraphics gfx = XGraphics.FromPdfPage(page);

var styles = new[] { XFontStyleEx.Regular, XFontStyleEx.Bold, XFontStyleEx.Italic, XFontStyleEx.BoldItalic };
var fonts = new[] { "IPAexMincho", "Noto Serif CJK JP", "Noto Serif CJK JP SemiBold", "Noto Serif Light", "Noto Serif Cond" };   // フォントは英語名で指定する


var text = "こんにちは、世界！ Hello, world!";
int cnt = 0;
foreach(var fontname in fonts)
{
    foreach (var style in styles)
    {
        var font = new XFont(fontname, 20, style);
        var x = 50.0 + (cnt % 2) 400;
        var y = 50.0 + (cnt / 2) * 30;
        gfx.DrawString(text, font, XBrushes.Black, x, y);
        cnt++;
    }
}

document.Save("output.pdf");
```
ポイントはフォントを英語名で指定することです。
フォントファミリーの英語名の確認方法は以下の記事を参照

https://qiita.com/Cladophlebis/items/bdcc1c79d32547a2ca26

"\*.ttc"のフォントは使用できません。指定してもエラーになります。"\*.ttf", "\*.otf"のフォントは概ね使えそうです。[設定]>[個人用設定]>[フォント]でフォントファイルの形式を確認してください。当たり前ですが日本語の文字が登録されていないフォントで日本語を表示することはできません。

このフォント指定方法はレガシーを引きずった形なので、Bold以外のウェイト指定はフォントファミリー名に含める必要があったりします。

PDFSharp はフォントを常にPDFファイルに埋め込みます。これによりPDFを表示する環境によってフォントが再現されないという事態は起きませんが、ファイルはその分大きくなります。特にOpenTypeフォントでPostScriptアウトラインを持つフォントの場合、(文書で使用した文字だけではなく)フォント全体を埋め込みます。そのためPDFファイルサイズが非常に大きくなることがあります。

https://docs.pdfsharp.net/PDFsharp/Topics/Fonts/Font-Embedding.html

このサンプルプログラムでは、`Noto Serif CJK JP`がPostScriptアウトラインを持つフォントのため、PDFファイルサイズが58MBにもなりました。

## 出力結果

出力結果は以下の通り。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3996474/d80e4321-bcb4-92e1-05b9-b1be17fda5d8.png)

以下、私の環境で
`IPAexMincho` は実際には1種類のフォントファイルしかありません。Regularフォントが加工されて太字や斜体が出力されています。

`Noto Serif CJK JP`はRegularに対応するBoldはありますがItalicはありません。しかし斜体も出力されています。

`Noto Serif CJK JP SemiBold`は本来なら`Noto Serif CJK JP`のフォントファミリーに入るはずです。BoldやItalicを指定しても反映されないのはそのあたりが原因でしょう。

`Noto Serif Light`と`Noto Serif Cond`は英文フォントで本来`Noto Serif`フォントファミリーに入ります。
`Noto Serif Light`はItalicフォントファイルがあるのにBoldどころかItalicも反映されません。
`Noto Serif Cond`はフォントファイルが揃っているからか、Regular Bold Italic BoldItalicとも反映されます。
`Noto Serif Light`でBold指定することはないでしょうがItalicが効かないのは意外でした。

## Typefaceを使ったフォント指定
PDFSharp-WPFなら`public XFont(Typeface typeface, double emSize)`を使えばもっと素直に文字ウェイトや文字幅を指定することができます。
```csharp
var document = new PdfDocument();
var page = document.AddPage();
page.Size = PageSize.A4;
page.Orientation = PageOrientation.Landscape;
XGraphics gfx = XGraphics.FromPdfPage(page);

var fonts = new[] { "IPAexMincho", "Noto Serif CJK JP", "Noto Serif" };   // フォントは英語名で指定する
var styles = new[] { FontStyles.Normal, FontStyles.Italic };
var weights = new[] { FontWeights.Light, FontWeights.Normal, FontWeights.Bold };
var stretches = new[] { FontStretches.Normal, FontStretches.Condensed };

var text = "友達のWa!";
int cnt = 0;
foreach (var fontname in fonts)
{
    foreach (var style in styles)
    {
        foreach (var weight in weights)
        {
            foreach(var stretch in stretches)
            {
                var typeface = new Typeface(new FontFamily(fontname), style, weight, stretch);
                var font = new XFont(typeface, 20);
                var x = 50.0 + (cnt % 6) * 120;
                var y = 50.0 + (cnt / 6) * 30;
                gfx.DrawString(text, font, XBrushes.Black, x, y);
                cnt++;
            }
        }
    }
}

document.Save("output.pdf");
```
こちらのフォントファミリーも英語名で指定します。タイプフェイスの指定は新しい方法に沿ったものですので以下の記事を見た方が良いでしょう。

https://qiita.com/Cladophlebis/items/1c4a36d7b171d84ff5a0

## 出力結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3996474/51b48a70-74a0-991b-39f3-73f77e8fd5b9.png)

以下、私の環境で
`IPAexMincho` は1種類のフォントファイルしかありません。LightウェイトやCondensed幅は表現されず、BoldウェイトとItalicスタイルは加工されたものが出力されています。

`Noto Serif CJK JP`もCondensed幅のフォントはなく、Normal幅で表現されます。ウェイトはLightもBoldもあります(LightとNormalの区別がつきにくいですが)。Italicはありませんが加工されたものが出力されています。

|スタイル|ウェイト|幅       |存在|表現|
|--------|--------|---------|----|----|
|Normal  |Light   |Normal   |○  |○   |
|Normal  |Light   |Condensed|-   |-(Normal-Light-Normal)   |
|Normal  |Normal  |Normal   |○  |○   |
|Normal  |Normal  |Condensed|-   |-(Normal-Normal-Normal)   |
|Normal  |Bold    |Normal   |○  |○   |
|Normal  |Bold    |Condensed|-   |-(Normal-Bold-Normal)   |
|Italic  |Light   |Normal   |-   |○   |
|Italic  |Light   |Condensed|-   |-(Italic-Light-Normal)   |
|Italic  |Normal  |Normal   |-   |○   |
|Italic  |Normal  |Condensed|-   |-(Italic-Normal-Normal)   |
|Italic  |Bold    |Normal   |-   |○   |
|Italic  |Bold    |Condensed|-   |-(Italic-Bold-Normal)   |

`Noto Serif`は種類が揃っていて、フォントのない２種類以外は正しく表現されています。
フォントが無い場合のフォールバックはウェイト(weight)よりも幅(stretch)を優先して採用するようです。

|スタイル|ウェイト|幅       |存在|表現|
|--------|--------|---------|----|----|
|Normal  |Light   |Normal   |○  |○   |
|Normal  |Light   |Condensed|-   |-(Normal-Normal-Condensed)   |
|Normal  |Normal  |Normal   |○  |○   |
|Normal  |Normal  |Condensed|○  |○   |
|Normal  |Bold    |Normal   |○  |○   |
|Normal  |Bold    |Condensed|○  |○   |
|Italic  |Light   |Normal   |○  |○   |
|Italic  |Light   |Condensed|-   |-(Italic-Normal-Condensed)   |
|Italic  |Normal  |Normal   |○  |○   |
|Italic  |Normal  |Condensed|○  |○   |
|Italic  |Bold    |Normal   |○  |○   |
|Italic  |Bold    |Condensed|○  |○   |