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

namespace DsPdfWeb.Demos.Basics
{
    // バランスの取れた列を含む複数列のテキストレイアウトを作成します。
    // このサンプルの中心は TextLayout.SplitAndBalance() メソッドで、
    // 複数の列間でテキストを分割し、それらの列の高さが近くなるように
    // バランスを取って、雑誌や新聞のようなテキストレイアウトを作成する
    // ことができます。
    public class BalancedColumns
    {
        public int CreatePDF(Stream stream)
        {
            var doc = new GcPdfDocument();
            const string fontname = "Yu Gothic";
            var fontSize = 10;
            // 1/2" の余白(72 dpi は DsPdf で使用されるデフォルトの解像度です)。
            var margin = 72 / 2;
            var pageWidth = doc.PageSize.Width;
            var pageHeight = doc.PageSize.Height;
            var cW = pageWidth - margin * 2;
            // 章のタイトルのテキスト書式。
            var tlCaption = new TextLayout(72);
            tlCaption.DefaultFormat.FontName = fontname;
            tlCaption.DefaultFormat.FontSize = fontSize + 4;
            tlCaption.DefaultFormat.Underline = true;
            tlCaption.MaxWidth = pageWidth;
            tlCaption.MaxHeight = pageHeight;
            tlCaption.MarginLeft = tlCaption.MarginTop = tlCaption.MarginRight = tlCaption.MarginBottom = margin;
            tlCaption.TextAlignment = TextAlignment.Center;
            // 章のキャプションの高さ(簡略化のために const を使用。
            const float captionH = 24;
            // メインドキュメントの本文のテキストレイアウト(デフォルトの DsPdf 解像度は 72 dpi です)。
            var tl = new TextLayout(72);
            tl.DefaultFormat.FontName = fontname;
            tl.DefaultFormat.FontSize = fontSize;
            tl.FirstLineIndent = 72 / 2;
            tl.MaxWidth = pageWidth;
            tl.MaxHeight = pageHeight;
            tl.MarginLeft = tl.MarginRight = tl.MarginBottom = margin;
            tl.MarginTop = margin + captionH;
            tl.ColumnWidth = cW * 0.3f;
            tl.TextAlignment = TextAlignment.Justified;
            // 追加の列を制御する PageSplitArea の配列。
            // (第1列は 'main' TextLayout によって制御されますが、追加するたびに PageSplitArea を指定する必要があります。
            // 作成すると、その列を描画するために使用できる TextLayout が返されます)
            var psas = new PageSplitArea[]
            {
                new PageSplitArea(tl) { MarginLeft = tl.MarginLeft + (cW * 0.35f) },
                new PageSplitArea(tl) { ColumnWidth = -cW * 0.3f }
            };
            // ページ間のテキストの分割を制御する分割オプションです。
            var tso = new TextSplitOptions(tl)
            {
                RestMarginTop = margin,
                MinLinesInFirstParagraph = 2,
                MinLinesInLastParagraph = 2
            };
            // いくつかの章を生成し、それぞれにアウトラインエントリーを提供します。
            const int NChapters = 20;
            doc.Pages.Add();
            for (int i = 0; i < NChapters; ++i)
            {
                // すべての列にわたる章ヘッダを出力します。
                string chapter = $"第 {i + 1} 章";
                tlCaption.Clear();
                tlCaption.Append(chapter);
                tlCaption.PerformLayout(true);
                doc.Pages.Last.Graphics.DrawTextLayout(tlCaption, PointF.Empty);
                // 章のアウトラインノードを追加します。
                doc.Outlines.Add(new OutlineNode(chapter, new DestinationFitV(doc.Pages.Count - 1, null)));
                //
                // 最終章のテキストをクリアし、新しい章を追加します。
                tl.FirstLineIsStartOfParagraph = true;
                tl.LastLineIsEndOfParagraph = true;
                tl.Clear();
                tl.Append(Common.Util.getString_ja(1, 1, 5, 2, 30, true));
                tl.PerformLayout(true);
                // 最終章の最後の下部位置を保持します。
                float contentBottom = 0f;
                // 章を出力します。
                var tls = new TextLayoutSplitter(tl);
                while (true)
                {
                    var tlCol0 = tls.SplitAndBalance(psas, tso);
                    var g = doc.Pages.Last.Graphics;
                    g.DrawTextLayout(tlCol0, PointF.Empty);
                    g.DrawTextLayout(psas[0].TextLayout, PointF.Empty);
                    g.DrawTextLayout(psas[1].TextLayout, PointF.Empty);
                    if (tls.SplitResult != SplitResult.Split)
                    {
                        // 章の終わり - 次の章のページにどれだけの高さが残っているかを調べます。
                        contentBottom = tl.ContentY + tl.ContentHeight;
                        contentBottom = Math.Max(contentBottom, psas[0].TextLayout.ContentRectangle.Bottom);
                        contentBottom = Math.Max(contentBottom, psas[1].TextLayout.ContentRectangle.Bottom);
                        // 章の出力を終えます。
                        break;
                    }
                    // 新しいページに章の出力を継続します。
                    psas[0].MarginTop = psas[1].MarginTop = margin;
                    doc.Pages.Add();
                }
                // 次の章 - 新しい章を開始するのに十分なスペースが現在のページに残っているかどうかを調べます。
                if (contentBottom + captionH < pageHeight * 0.8f)
                {
                    // 現在のページで新しい章を開始します。
                    contentBottom += pageHeight * 0.05f;
                    tlCaption.MarginTop = contentBottom;
                    tl.MarginTop = psas[0].MarginTop = psas[1].MarginTop = contentBottom + captionH;
                }
                else if (i < NChapters - 1)
                {
                    // 新しいページで新しい章を開始します。
                    tlCaption.MarginTop = margin;
                    tl.MarginTop = psas[0].MarginTop = psas[1].MarginTop = margin + captionH;
                    doc.Pages.Add();
                }
            }
            // PDF ドキュメントを保存します。
            doc.Save(stream);
            return doc.Pages.Count;
        }
    }
}