今日はいつもにもまして自分用 Tips.
PDFのドキュメントを作成する必要があって、ライブラリを調査してみた。今の希望は、特定のフォーマットの文章を簡単に作る事。
iTextSharp
ゼロからPDFをがっつりレイアウトして作れる。
にだいたい書いている。サンプル書いてみた。コードは見ての通りで、ゴリゴリに座標を指定して、テキストとかラインとかを置いていく感じ。
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の座標をどう考えるか?
このページに解説があった 1pt ≒ 0.352777mm のポイント単位で計算。
A4 の場合、約 595x842 pt ⇔ 210x297 mm
出来た。
感想
めんどい。ざっくりとしたのを作りたいだけなのに、座標の指定とかフォントの指定とか面倒すぎる。ちなみに、私は、あるプロジェクトの検証用になんでもいいからPDFを生成するサンプルを書きたかっただけだ。もっと楽なアプローチはないだろうか?
CommonMark.NET + HtmlRenderer.PdfSharp
そうだ、誰かがレイアウトをしてくれたらいいじゃないの?ということは、MDから、PDFに変換してくれるようなものがあれば最高じゃね?と思って調べたのがこちら。
流石に一発変換では無理だけど、MD -> HTML, HTML -> PDF ならイケるとわかった。
コード
スパイクなので冗長なコードが入っているけど、こいつで、MD-> HTML変換。しかもこのライブラリは昔あったライブラリよりずっと高速らしい。
            var html = CommonMark.CommonMarkConverter.Convert(md);
これで、HTML -> PDF 変換。これは楽すぎる。
           using (MemoryStream ms = new MemoryStream())
            {
                var pdf = TheArtOfDev.HtmlRenderer.PdfSharp.PdfGenerator.GeneratePdf(html, PdfSharp.PageSize.A4);
                pdf.Save(ms);
                result = ms.ToArray();
            }
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);
            }
        }
    }
}
結果
これは楽だ。これでいいんじゃない?
私はサンプルだから、こんなもんでいいけど、ガチだと、例えば、同じMD でも、GitHub みたいなもうちょっとカッコよいレイアウトにしたいとか思いそう。その場合は、wikiに書いてあったけど、HtmlFormatter.csを継承して、好きな部分をオーバーライドしてあげればいいみたい。
まとめ
HTMLに一旦変換してからやると、世界は広がって楽になった! ちなみに、HTMLからPDFのライブラリは、C#ネイティブでなくてよければ、wkhtmltopdfというのがあるらしい。ちなみに、印刷の実行に関しては PrinterSettings クラスで可能っぽい。サンプルがついている。