C# の PDF作成用ライブラリ

今日はいつもにもまして自分用 Tips. 

PDFのドキュメントを作成する必要があって、ライブラリを調査してみた。今の希望は、特定のフォーマットの文章を簡単に作る事。

# iTextSharp 

ゼロからPDFをがっつりレイアウトして作れる。

* [iTextSharpでPDF出力](https://qiita.com/mistolteen/items/c94ad35a9740748440fc)

にだいたい書いている。サンプル書いてみた。コードは見ての通りで、ゴリゴリに座標を指定して、テキストとかラインとかを置いていく感じ。

```csharp
using iTextSharp.text;
using iTextSharp.text.pdf;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PDFSample
{
    class Program
    {
        static void Main(string[] args)
        {

            var doc = new Document(PageSize.A4.Rotate());
            var stream = new MemoryStream();
            
            var pw = PdfWriter.GetInstance(doc, stream);

            doc.Open();
            var pdfContentByte = pw.DirectContent;
            var bf = BaseFont.CreateFont(@"c:\windows\fonts\msgothic.ttc,0", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

            // A4 595x842 pt = 210x297  
            
            pdfContentByte.SetFontAndSize(bf, 24);
            ShowTextAligned(pdfContentByte, 20, 561, "Fitness Club Membership");

            DrawLine(pdfContentByte, 2, 551, 840, 551);
            
            pdfContentByte.SetFontAndSize(bf, 11);
            ShowTextAligned(pdfContentByte, 20, 530, "You can enojy Muscle Training and high-quality protein.");

            pdfContentByte.SetFontAndSize(bf, 18);
            ShowTextAligned(pdfContentByte, 20, 480, "Name: Mr.Tsuyoshi Ushio Age: 46");
            ShowTextAligned(pdfContentByte, 20, 457, "Membership: Gold");

            doc.Close();

            using (BinaryWriter w = new BinaryWriter(File.OpenWrite(@"result.pdf"))) {
                w.Write(stream.ToArray());
            }
            Console.WriteLine("See the result.pdf");
        }
        private static void ShowTextAligned(PdfContentByte pdfContentByte, float x, float y, string text, int alignment = Element.ALIGN_LEFT, float rotaion = 0)
        {
            pdfContentByte.BeginText();
            pdfContentByte.ShowTextAligned(alignment, text, x, y, rotaion);
            pdfContentByte.EndText();
        }
        private static void DrawLine(PdfContentByte pdfContentByte, float fromX, float fromY, float toX, float toY)
        {
            pdfContentByte.MoveTo(fromX, fromY);
            pdfContentByte.LineTo(toX, toY);
            pdfContentByte.ClosePathStroke();
        }
    }
}

````

難しくないけど、相当ゴリゴリ。わからなかったは、PDFの座標をどう考えるか？

* [iTextSharp.text.Rectangle 座標系 ](http://to-annot-pdf.blogspot.jp/2015/07/itextsharptextrectangle.html) 

このページに解説があった `1pt ≒ 0.352777mm` のポイント単位で計算。

> A4 の場合、約 595x842 pt ⇔ 210x297 mm

出来た。

<img width="883" alt="2017-12-23_22h43_18.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/bed06d93-2c2c-dabe-3c40-2d734d966eba.png">

## 感想

めんどい。ざっくりとしたのを作りたいだけなのに、座標の指定とかフォントの指定とか面倒すぎる。ちなみに、私は、あるプロジェクトの検証用になんでもいいからPDFを生成するサンプルを書きたかっただけだ。もっと楽なアプローチはないだろうか？

# CommonMark.NET + HtmlRenderer.PdfSharp

そうだ、誰かがレイアウトをしてくれたらいいじゃないの？ということは、MDから、PDFに変換してくれるようなものがあれば最高じゃね？と思って調べたのがこちら。

* [CommonMark.NET](https://github.com/Knagis/CommonMark.NET)
* [HtmlRenderer.PdfSharp](https://www.nuget.org/packages/HtmlRenderer.PdfSharp/)

流石に一発変換では無理だけど、`MD -> HTML`, `HTML -> PDF` ならイケるとわかった。

## コード

スパイクなので冗長なコードが入っているけど、こいつで、MD-> HTML変換。しかもこのライブラリは昔あったライブラリよりずっと高速らしい。

```csharp
            var html = CommonMark.CommonMarkConverter.Convert(md);
```
これで、HTML -> PDF 変換。これは楽すぎる。

```csharp
           using (MemoryStream ms = new MemoryStream())
            {
                var pdf = TheArtOfDev.HtmlRenderer.PdfSharp.PdfGenerator.GeneratePdf(html, PdfSharp.PageSize.A4);
                pdf.Save(ms);
                result = ms.ToArray();
            }
```


```csharp
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MdToPDF
{
    class Program
    {
        static void Main(string[] args)
        {
            var md = @"
# Fitness club membership

## Mr.Tsuyoshi Ushio

You are the member of the muscle fitness.

* Zama branch membership
* Gold membershiop
* Protein service included

## Message from manger

You can enjoy muscle training with great equipments. Grow your muscle!
";
            var byteArray = Encoding.UTF8.GetBytes(md);
            var stream = new MemoryStream(byteArray);

            using (var reader = new System.IO.StreamReader(stream))
            using (var writer = new System.IO.StreamWriter("result.html"))
            {
                CommonMark.CommonMarkConverter.Convert(reader, writer);
            }

            var html = CommonMark.CommonMarkConverter.Convert(md);
            Console.WriteLine(html);
            Console.ReadLine();

            Byte[] result = null;
            using (MemoryStream ms = new MemoryStream())
            {
                var pdf = TheArtOfDev.HtmlRenderer.PdfSharp.PdfGenerator.GeneratePdf(html, PdfSharp.PageSize.A4);
                pdf.Save(ms);
                result = ms.ToArray();
            }
            using (BinaryWriter w = new BinaryWriter(File.OpenWrite(@"result.pdf")))
            {
                w.Write(result);
            }


        }
    }
}
```

## 結果

これは楽だ。これでいいんじゃない？

<img width="653" alt="pdf.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/92a85b42-0b7c-9bd6-0ea5-05bda044e546.png">

私はサンプルだから、こんなもんでいいけど、ガチだと、例えば、同じMD でも、GitHub みたいなもうちょっとカッコよいレイアウトにしたいとか思いそう。その場合は、[wiki](https://github.com/Knagis/CommonMark.NET/wiki)に書いてあったけど、[HtmlFormatter.cs](https://github.com/Knagis/CommonMark.NET/blob/master/CommonMark/Formatters/HtmlFormatter.cs)を継承して、好きな部分をオーバーライドしてあげればいいみたい。


# まとめ

HTMLに一旦変換してからやると、世界は広がって楽になった！　ちなみに、HTMLからPDFのライブラリは、C#ネイティブでなくてよければ、[wkhtmltopdf](https://wkhtmltopdf.org/)というのがあるらしい。ちなみに、印刷の実行に関しては [PrinterSettings クラス](https://msdn.microsoft.com/ja-jp/library/system.drawing.printing.printersettings(v=vs.110).aspx)で可能っぽい。サンプルがついている。

* [iTextSharpでPDF出力](https://qiita.com/mistolteen/items/c94ad35a9740748440fc)
* [Convert HTML to PDF in .NET](https://stackoverflow.com/questions/564650/convert-html-to-pdf-in-net)
* [サンプルコードのリポジトリ](https://github.com/TsuyoshiUshio/PDFSpike)
* [CommonMark.NET](https://github.com/Knagis/CommonMark.NET)
* [HtmlRenderer.PdfSharp](https://www.nuget.org/packages/HtmlRenderer.PdfSharp/)