Blazor ServerでPDFをサイレント印刷する方法（無料のWebView2使用）

# はじめに
お仕事にて`Classic asp`および`ASP.NET`で作成された既存アプリケーションを`Blazor Server`に作り替える作業をしています。
ラベルプリンターの印刷があるシステムの改修する段階となり、使用している外部モジュールなどもバージョンアップしました。

https://qiita.com/yaju/items/cd54a0bf46b1bfec4a8f

ただ`FreeSpire.PDF`のバージョン 10.2.0にしたところ、印刷メソッドが削除されていました。一つ前のバージョン 8.6.0は問題ないため、このバージョンにしています。

https://www.reddit.com/r/csharp/comments/1bl6y3q/comment/kw3k99w/

## 小さい文字の印字
ラベルプリンターの印刷なので小さい文字が必要となります。
MSゴシック 6ptの文字を印字すると、`Acrobat Reader`では印字した場合は細い線を保ったまま綺麗な文字で印字されるが、`FreeSpire.PDF`だと細い線が汚く印字される。もちろんその状態でも問題なく読めるので運用上は困ることはない。綺麗な状態を知っていると残念なところである。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/4b78a44e-f51a-4b53-aafc-0f9db17eb6f9.png)

上が`Acrobat Reader`による印字、下が`FreeSpire.PDF`による印字
`ghostscript.net`も試してみましたが、`FreeSpire.PDF`と同じ印字状態でした。

フォントを調整することで、そこそこマシになりました。

# 本題
`Acrobat Reader`の代替になるものを考えた時に、`Microsoft　Edge`で`PDF`を表示および印刷が出来ることに気が付きました。

https://learn.microsoft.com/ja-jp/microsoft-edge/webview2/how-to/print?tabs=dotnetcsharp

`WebView2`コントロールを使用して、PDFファイルを印刷するようにしたいと考えました。その際に印刷ダイアログ画面を出さないサイレント印刷を行う。

:::note
Acrobat Reader/Adobe Reader をサーバーにインストールし、ネットワーク経由で使用する方法は原則的に禁止されています。
[Acrobat Reader/Adobe Reader のサーバー利用について](https://helpx.adobe.com/jp/x-productkb/policy-pricing/cpsid_87204.html)
:::

## 印刷プログラムの作成
`WebView2`コントロールは`Windows Forms`アプリケーション（または`WPF`）のコンテキスト内で動作するように設計されています。

印刷プログラムとして「WebView2PdfPrinter.exe」を作成します。

1. `Windows Forms`アプリケーション（または`WPF`）で、`WebView2`コントロールを貼り付けます。
2. コマンドライン引数または設定ファイルで、印刷に必要なパラメーター(最低でもPDFファイルのパス名とプリンター名)を渡します。
3. LoadイベントでPrintPdfメソッドを呼びます。 
4. ビルドしたexeを実行して、パラメーターのpdfファイルが印刷できることを確認します。

```cs
private void frmMain_Load(object sender, EventArgs e)
{
    // コマンドライン引数を取得
    string[] args = Environment.GetCommandLineArgs();
    string pdfPath = args[1];
    string printerName = args[2];
    int count;
    int.TryParse(args[3], out count);
    if (count == 0) count = 1;

    // 印刷処理
    PrintPdf(pdfPath, printerName, count);
}

public void PrintPdf(string pdfPath, string printerName, int count)
{
    webView2.Source = new Uri(pdfPath);
    webView2.NavigationCompleted += async (sender, e) =>
    {
        if (e.IsSuccess)
        {
           CoreWebView2PrintSettings printSettings = webView2.CoreWebView2.Environment.CreatePrintSettings();
           printSettings.PrinterName = printerName;
           printSettings.Copies = count;

           var printStatus = await webView2.CoreWebView2.PrintAsync(printSettings);
        }
    }
}
```

### PrintSettingsクラス
印刷に必要なパラメーターとして、PrintSettingsクラスをセットして渡します。
パラメーター数が多いのでコマンドラインで渡す場合は最小限とし、多くのパラメーターを活かしたいなら設定ファイルで読み込む方式にします。

https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2printsettings?view=webview2-winrt-1.0.2478.35

### DLL化の検討
普通にVisual Studioのテンプレートでクラスライブラリーを作成しても、`System.Windows.Forms`名前空間を使用できないんですが、下記の方法でDLL化することが出来ました。

https://qiita.com/sakai3578/items/245652225895bb56b4fb

上記サイトに従い、`csproj`ファイルをテキストエディタで開いて下記を追加します。

```xml
<UseWindowsForms>true</UseWindowsForms>
```

しかし、Blazor Serverで作成したDLLを参照して使用しようとすると、Blazor Server側にも`UseWindowsForms`の設定とNuGetでWebView2の参照が必要になってしまう。
それは嫌だなと思ってやめました。

## 印刷プログラムの呼び出し
Blazor Serverで印刷プログラムを呼び出して使用します。

今回は例として、「C:¥Tool¥WebView2PdfPrinter」フォルダに、`WebView2PdfPrinter.exe` と関連する`WebView2`などのモジュールを置きます。

Blazor Serverのサンプルアプリケーションで、カウントボタンをクリックしたら印刷されるようにします。
PDFファイルは「LBL00001.pdf」、プリンター名は「EPSON PX-501A」、部数は「1」を指定します。

```cs
@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;

        Task.Run(() => Print());
    }

    private void Print()
    {
        string directory = @"C:\Tool\WebView2PdfPrinter\";
        string toolFilepath = Path.Combine(directory, "WebView2PdfPrinter.exe");
        string pdfPath = Path.Combine(directory, "LBL00001.pdf");
        string printerName = "EPSON PX-501A";
        int count = 1;

        var processStartInfo = new ProcessStartInfo
        {
            FileName = toolFilepath,
            Arguments = $"{pdfPath} \"{printerName}\" {count}",
            WindowStyle = ProcessWindowStyle.Hidden,
            CreateNoWindow = true,
            RedirectStandardOutput = true,
            UseShellExecute = false,
        };

        using (var process = Process.Start(processStartInfo)) {
            // タイムアウト5秒を設定してプロセスの完了を待つ
            if (!process.WaitForExit(5000))
            {
                // プロセスを強制終了する
                process.Kill();
            }
        }
    }
}
```

## PDFのサイレント印刷
IIS用に「C:\inetpub\wwwroot\print」フォルダに発行し、Webブラウザで「localhost/print」してBlazor Serverのサンプルアプリケーションを動かします。
カウントボタンをクリックするとサイレント印刷されるようになります。

### 配置の注意点
当初は印刷プログラムを「C:¥Tool¥Printer」フォルダではなく、「C:\inetpub\wwwroot\Printer\WebView2PdfPrinter」フォルダに配置したのですが、ここに配置すると権限の問題なのか印刷が出来なくなります。
調査のために色々試してみたのですがPDF表示がそもそも出来なかったです。

inetpubフォルダ以外なら印刷できるので、とりあえずヨシとしました。

# 最後に
PDFの編集するライブラリーは多くありますが、印刷までサポートしているのは`Spire.PDF`など少ししかありません。ただ無料版の`FreeSpire.PDF`のバージョン 10.2.0にしたところ、印刷メソッドが削除されていました。
PDFを印刷するには画像に変換する必要があるからハードルが高いようです。

WebView2を使用することでサーバー上からPDFのサイレント印刷が可能となります。

今回、Process.Startで呼び出す方式にしましたが、常駐プログラムにして特定の場所にファイルが配置されたら印刷するようにしてもいいと思います。