DynamicTable.cs
// 
// このコードは、DioDocs for PDF のサンプルの一部として提供されています。
// © MESCIUS inc. All rights reserved.
// 
using System;
using System.IO;
using System.Drawing;
using System.Text;
using GrapeCity.Documents.Pdf;
using GrapeCity.Documents.Text;
using GrapeCity.Documents.Html;

namespace DsPdfWeb.Demos
{
    // このサンプルでは、1ページに収まらない可能性があるさまざまな行数の
    // HTML テーブルを、ページ上の任意の位置から始まるPDFドキュメントに
    // 挿入する方法を示します(ただし、すべてのデータ行は同じ高さでなけ
    // ればなりません)。
    // 最初に単一のデータ行を持つテーブルを作成し、その高さを測定します。
    // 次に、2つのデータ行を持つ同様のテーブルを作成し、それも測定します。
    // これにより、ヘッダーとデータ行の高さを確認し、テーブルをページ上の
    // 目的の位置から PDF にレンダリングし、必要に応じて追加のページに
    // 分割できます。
    // 
    // DsHtml をプロジェクトに追加する方法の詳細については、
    // HelloWorldHtml サンプルコードの上部にあるコメントのメモを
    // 参照してください。
    public class DynamicTable
    {
        public int CreatePDF(Stream stream)
        {
            // このタグは、CSS スタイルなどを定義するHTMLページテンプレートに
            // 準備済みテーブル HTML コードを挿入するために使用されます。
            // (このタグを使用すると、表の HTML コードを作成するときに
            // string.Format を使用できます。そうしないと、スタイル定義内の
            // 中括弧が書式指定子に干渉するためです。)
            const string TTAG = "___TABLE___";

            // HTML ページテンプレート。
            const string tableTpl =
                "<!DOCTYPE html>" +
                "<html>" +
                "<head>" +
                "<style>" +
                "#employees {" +
                "  font-family: 'Trebuchet MS', Arial, Helvetica, sans-serif;" +
                "  border-collapse: collapse;" +
                "  width: 100%;" +
                "}" +

                "#employees td, #employees th {" +
                "  border: 1px solid #ddd;" +
                "  padding: 8px;" +
                "}" +

                "#employees tr:nth-child(even){background-color: #f2f2f2;}" +

                "#employees tr:hover {background-color: #ddd;}" +

                "#employees th {" +
                "  padding-top: 12px;" +
                "  padding-bottom: 12px;" +
                "  text-align: left;" +
                "  background-color: #3377ff;" +
                "  color: white;" +
                "}" +
                "</style>" +
                "</head>" +
                "<body>" +

                TTAG +

                "</body>" +
                "</html>";

            // テーブルの HTML コード書式。
            const string tableFmt =
                "<table id='employees'>" +
                "  <tr>" +
                "    <th>Index</th>" +
                "    <th>Lorem</th>" +
                "    <th>Ipsum</th>" +
                "  </tr>" +
                "{0}" +
                "</table>";

            // テーブル行の HTML コード書式。
            const string dataRowFmt =
                "  <tr>" +
                "    <td>{0}</td>" +
                "    <td>{1}</td>" +
                "    <td>{2}</td>" +
                "  </tr>";

            // 新規 PDF ドキュメントを作成します。
            var doc = new GcPdfDocument();
            // ページを追加します。
            var page = doc.NewPage();
            // ページを追加し、そのグラフィックを取得します。
            var g = page.Graphics;

            // HTML から PDF へのフォーマットオプションを設定します。
            // 最も重要なのはサイズの制限です。この場合、プログラムで調整
            // するため、高さは制限しません。
            // HtmlToPdfFormat では、サイズはインチで指定されることに注意してください。
            var hf = new HtmlToPdfFormat(false) { MaxPageWidth = page.Size.Width / 72 };

            // 単一のデータ行の HTML コード(サンプルデータ付き)。
            var dataRow = string.Format(dataRowFmt, "a", "b", "c");
            // 単一のデータ行を持つテーブルを含む HTML ページ。
            var thtml = tableTpl.Replace(TTAG, string.Format(tableFmt, dataRow));

            // HTML のレンダリングに使用する GcHtmlBrowser のインスタンスを生成します。
            using var browser = Common.Util.NewHtmlBrowser();

            // 現在の GcPdfGraphics の HTML を測定します。
            var s1 = g.MeasureHtml(browser, thtml, hf);
            // 同じ HTML ページですが、2つのデータ行があります。
            thtml = tableTpl.Replace(TTAG, string.Format(tableFmt, dataRow + dataRow));
            // 新しい HTML を測定します。
            var s2 = g.MeasureHtml(browser, thtml, hf);
            // データ行とヘッダー行の高さを計算します。
            var rowHeight = s2.Height - s1.Height;
            var headerHeight = s1.Height - rowHeight;

            // 最初のページの上部にメモを追加します。
            var nrc = Common.Util.AddNote(
                "ここでは、最初のページの指定された位置から始まり、複数のページにまたがる" +
                "可能性のある未知の行数で HTML テーブルをレンダリングします。",
                page);

            // ランダムデータでテーブルを構築するための設定。
            var lorems = Common.Util.LoremWords();
            var rnd = Common.Util.NewRandom();
            var sb = new StringBuilder();

            // ページレイアウトパラメータ。
            var marginx = nrc.Left;
            var marginy = nrc.Top;
            var x = marginx;
            var y = nrc.Bottom + 36;
            var tbottom = nrc.Bottom + 36 + headerHeight;
            // レンダリングするランダムな数のデータ行。
            int nrows = rnd.Next(100, 200);
            // テーブルを生成してレンダリングし、必要に応じて継続ページを追加します。
            for (int i = 0; i < nrows; ++i)
            {
                sb.AppendFormat(dataRowFmt, i, lorems[rnd.Next(lorems.Count)], lorems[rnd.Next(lorems.Count)]);
                tbottom += rowHeight;
                var lastPage = i == nrows - 1;
                if (tbottom >= page.Size.Height - 72 || lastPage)
                {
                    var html = tableTpl.Replace(TTAG, string.Format(tableFmt, sb.ToString()));
                    var ok = g.DrawHtml(browser, html, x, y,
                        new HtmlToPdfFormat(false) { MaxPageWidth = (page.Size.Width - marginx * 2) / 72 },
                        out SizeF size);
                    if (!lastPage)
                    {
                        page = doc.NewPage();
                        g = page.Graphics;
                        y = 72;
                        tbottom = y + headerHeight;
                        sb.Clear();
                    }
                }
            }

            // PDF ドキュメントを保存します。
            doc.Save(stream);
            return doc.Pages.Count;
        }
    }
}