LayoutDemos.cs
// 
// このコードは、DioDocs for PDF のサンプルの一部として提供されています。
// © MESCIUS inc. All rights reserved.
// 
using System;
using System.IO;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using GrapeCity.Documents.Pdf;
using GrapeCity.Documents.Drawing;
using GrapeCity.Documents.Text;
using GrapeCity.Documents.Imaging;
using GrapeCity.Documents.Layout;
using GrapeCity.Documents.Layout.Composition;
using GCTEXT = GrapeCity.Documents.Text;
using GCDRAW = GrapeCity.Documents.Drawing;

namespace DsPdfWeb.Demos
{
    // この統合サンプルでは、GrapeCity.Documents.Layout.Composition 名前空間の
    // ヘルパークラスを使用して、独自の Z オーダーやクリッピングを持つ
    // 複雑で柔軟な制約ベースのレイアウトを作成する方法を紹介しています。
    public class LayoutDemos
    {
        static readonly Color
            PageColor = Color.FromArgb(39, 41, 43),
            BoxColor = Color.FromArgb(230, 230, 230),
            CodeColor = Color.FromArgb(0, 77, 102),
            RectColor = Color.FromArgb(64, 126, 148),
            DescColor = Color.White;
        delegate void DrawLayoutSample(GcGraphics g, Size pageSize);
        static readonly Dictionary<string, DrawLayoutSample> c_samples = new Dictionary<string, DrawLayoutSample>()
        {
            { "DrawSample1", DrawSample1 },
            { "DrawSample2", DrawSample2 },
            { "DrawSample3", DrawSample3 },
            { "DrawSample4", DrawSample4 },
            { "DrawSample5", DrawSample5 },
            { "DrawSample6", DrawSample6 },
            { "DrawSample7", DrawSample7 },
            { "DrawSample8", DrawSample8 },
        };

        public int CreatePDF(Stream stream, int paramsIdx = 0)
        {
            return CreatePDF(stream, GetSampleParamsList()[paramsIdx]);
        }

        public int CreatePDF(Stream stream, string[] sampleParams)
        {
            if (!c_samples.TryGetValue(sampleParams[3], out DrawLayoutSample drawSample))
                throw new Exception($"不明なパラメータのサンプル:{sampleParams[3]}");

            var doc = new GcPdfDocument();
            var page = doc.NewPage();
            if (sampleParams[3] == "DrawSample1")
                page.Landscape = true;
            var g = page.Graphics;
            g.Resolution = 96;

            drawSample(g, g.CanvasSize.ToSize());

            // PDF を保存します。
            doc.Save(stream);
            return doc.Pages.Count;
        }

        public GcBitmap GenerateImage(Size pixelSize, float dpi, bool opaque, string[] sampleParams = null)
        {
            if (!c_samples.TryGetValue(sampleParams[3], out DrawLayoutSample drawSample))
                throw new Exception($"不明なパラメータのサンプル:{sampleParams[3]}");

            var bmp = new GcBitmap(pixelSize.Width, pixelSize.Height, opaque, dpi, dpi);
            using var g = bmp.CreateGraphics(PageColor);
            drawSample(g, pixelSize);

            return bmp;
        }

        public static List<string[]> GetSampleParamsList()
        {
            return new List<string[]>()
            {
                // この文字列は、各サンプルの名前、説明、サンプルIDです。
                new string[] { "図 1-4", "図 1-4:水平および垂直の配置制約",
                    null,
                    "DrawSample1" },
                new string[] { "図 5", "図 5:2 つの Visual に固定された境界制約",
                    null,
                    "DrawSample2" },
                new string[] { "図 6", "図 6:比率指定の幅を含む水平方向のチェーン",
                    null,
                    "DrawSample3" },
                new string[] { "図 7", "図 7:長方形と余白が均等に配置された水平方向のチェーン",
                    null,
                    "DrawSample4" },
                new string[] { "図 8", "図 8:長方形が均等に配置された水平方向のチェーン(余白なし)",
                    null,
                    "DrawSample5" },
                new string[] { "図 9", "図 9:余白なしの比率による水平方向のチェーン",
                    null,
                    "DrawSample6" },
                new string[] { "図 10", "図 10:比率指定の余白と一纏めにされた長方形を含む水平方向のチェーン",
                    null,
                    "DrawSample7" },
                new string[] { "テキストフロー", "非矩形の輪郭の中や周囲にテキストを描画",
                    null,
                    "DrawSample8" },
            };
        }

        // 図 1-4:水平および垂直の配置制約
        static void DrawSample1(GcGraphics g, Size _)
        {
            g.Transform = Matrix3x2.CreateScale(1.4f);

            // Surface オブジェクトは Visual をレイアウトしグラフィックス上に描画することができます。
            var surf = new Surface();

            // View1

            // View オブジェクトは、変換行列を持つ Visual のグループを表します。
            var view1 = surf.CreateView(250, 150).Translate(50, 40);

            // Visual は、その配置に使用される関連する LayoutRect と、
            // そのコンテンツをグラフィックス上に描画するデリゲートを含む要素です。
            var v = view1.CreateVisual(DrawView);
            v.Tag = new FigureCaption(1, "親に対する水平方向の配置制約:",
                "rA.SetLeft(null, AnchorParam.Left, 90);");
            v.LayoutRect.AnchorExact(null);

            v = view1.CreateVisual(DrawRect);
            var rA = v.LayoutRect;
            rA.SetLeft(null, AnchorParam.Left, 90);
            rA.SetWidth(70);
            rA.SetHeight(40);
            rA.SetVerticalCenter(null, AnchorParam.VerticalCenter);
            v.Tag = "A";

            var r = view1.CreateVisual(DrawLineWithLeftArrow).LayoutRect;
            r.AnchorTopBottom(rA, 0, 0);
            r.SetLeft(null, AnchorParam.Left);
            r.SetRight(rA, AnchorParam.Left);

            // View2
            var view2 = surf.CreateView(250, 150).Translate(350, 40);
            v = view2.CreateVisual(DrawView);
            v.Tag = new FigureCaption(2, "オフセットされた水平方向の配置制約:",
                "rB.SetLeft(rA, AnchorParam.Left, 40);");
            v.LayoutRect.AnchorExact(null);

            v = view2.CreateVisual(DrawRect);
            rA = v.LayoutRect;
            rA.AnchorTopLeft(null, 15, 70, 110, 60);
            v.Tag = "A";

            v = view2.CreateVisual(DrawRect);
            var rB = v.LayoutRect;
            rB.SetLeft(rA, AnchorParam.Left, 40);
            rB.SetWidth(60);
            rB.SetHeight(35);
            rB.SetTop(rA, AnchorParam.Bottom, 20);
            v.Tag = "B";

            r = view2.CreateVisual(DrawVertLineWithLeftArrow).LayoutRect;
            r.SetTop(rA, AnchorParam.VerticalCenter);
            r.SetBottom(rB, AnchorParam.VerticalCenter);
            r.SetLeft(rA, AnchorParam.Left);
            r.SetRight(rB, AnchorParam.Left);

            // View3
            var view3 = surf.CreateView(250, 150).Translate(50, 260);
            v = view3.CreateVisual(DrawView);
            v.Tag = new FigureCaption(3, "水平方向と垂直方向の配置制約:",
                "rB.SetLeft(rA, AnchorParam.Right, 50);\n" +
                "rC.SetTop(rA, AnchorParam.Bottom, 40);");
            v.LayoutRect.AnchorExact(null);

            v = view3.CreateVisual(DrawRect);
            rA = v.LayoutRect;
            rA.AnchorTopLeft(null, 15, 30, 70, 40);
            v.Tag = "A";

            v = view3.CreateVisual(DrawRect);
            rB = v.LayoutRect;
            rB.SetLeft(rA, AnchorParam.Right, 50);
            rB.SetWidth(70);
            rB.SetHeight(40);
            rB.SetTop(rA, AnchorParam.Top);
            v.Tag = "B";

            v = view3.CreateVisual(DrawRect);
            var rC = v.LayoutRect;
            rC.SetTop(rA, AnchorParam.Bottom, 40);
            rC.SetWidth(70);
            rC.SetHeight(40);
            rC.SetLeft(rA, AnchorParam.Left);
            v.Tag = "C";

            r = view3.CreateVisual(DrawLineWithLeftArrow).LayoutRect;
            r.AnchorTopBottom(rA, 0, 0);
            r.SetLeft(rA, AnchorParam.Right);
            r.SetRight(rB, AnchorParam.Left);

            r = view3.CreateVisual(DrawLineWithUpArrow).LayoutRect;
            r.AnchorLeftRight(rA, 0, 0);
            r.SetTop(rA, AnchorParam.Bottom);
            r.SetBottom(rC, AnchorParam.Top);

            // View4
            var view4 = surf.CreateView(250, 150).Translate(350, 260);
            var layoutView = view4.LayoutView;
            v = view4.CreateVisual(DrawView);
            v.Tag = new FigureCaption(4, "ガイドラインに制約された矩形:",
                "var anchorPoint = layoutView.CreatePoint(0.25f, 0);\n" +
                "rA.SetLeft(anchorPoint, 60);");
            v.LayoutRect.AnchorExact(null);

            // AnchorPoint は、X軸またはY軸のガイドラインとして機能します。
            var anchorPoint = layoutView.CreatePoint(0.25f, 0);

            v = view4.CreateVisual(DrawVertGuideline);
            var rG = v.LayoutRect;
            rG.SetLeft(anchorPoint);
            rG.AnchorVerticalLine(null);
            v.Tag = "25 %";

            v = view4.CreateVisual(DrawRect);
            rA = v.LayoutRect;
            rA.SetLeft(anchorPoint, 60);
            rA.SetWidth(70);
            rA.SetHeight(40);
            rA.SetVerticalCenter(null, AnchorParam.VerticalCenter);
            v.Tag = "A";

            r = view4.CreateVisual(DrawLineWithLeftArrow).LayoutRect;
            r.AnchorTopBottom(rA, 0, 0);
            r.SetLeft(rG, AnchorParam.Left);
            r.SetRight(rA, AnchorParam.Left);

            surf.Render(g);
        }

        // 図 5:2 つの Visual に固定された境界制約
        static void DrawSample2(GcGraphics g, Size _)
        {
            g.Transform = Matrix3x2.CreateScale(2);

            var surf = new Surface();

            // View1
            var view1 = surf.CreateView(350, 160).Translate(30, 30);
            view1.CreateVisual(DrawView).LayoutRect.AnchorExact(null);

            var v = view1.CreateVisual(DrawRect);
            var rA = v.LayoutRect;
            rA.AnchorTopLeft(null, 30, 50, 60, 40);
            v.Tag = "A";

            v = view1.CreateVisual(DrawRect);
            var rB = v.LayoutRect;
            rB.AnchorTopLeft(null, 90, 50, 90, 40);
            v.Tag = "B";

            v = view1.CreateVisual(DrawVertGuideline);
            var rG = v.LayoutRect;

            // 同じ矩形に複数の MinLeft 制約を追加すると、
            // 他の矩形のベースとして使用される境界が作成されます。
            rG.AppendMinLeft(rA, AnchorParam.Right);
            rG.AppendMinLeft(rB, AnchorParam.Right);

            rG.AnchorVerticalLine(null);

            v = view1.CreateVisual(DrawRect);
            var rC = v.LayoutRect;
            rC.AppendMinLeft(rA, AnchorParam.Right, 50);
            rC.AppendMinLeft(rB, AnchorParam.Right, 50);
            rC.SetWidth(70);
            rC.SetHeight(40);
            rC.SetVerticalCenter(null, AnchorParam.VerticalCenter);
            v.Tag = "C";

            var r = view1.CreateVisual(DrawLineWithLeftArrow).LayoutRect;
            r.AnchorTopBottom(rC, 0, 0);
            r.SetLeft(rG, AnchorParam.Right);
            r.SetRight(rC, AnchorParam.Left);

            // View2
            var view2 = surf.CreateView(350, 160).Translate(30, 210);
            v = view2.CreateVisual(DrawView);
            v.Tag = new FigureCaption(5, "C は境界に制約され、\n" +
                "A と B の両方の位置と大きさに応じて移動:",
                "rC.AppendMinLeft(rA, AnchorParam.Right, 50);\n" +
                "rC.AppendMinLeft(rB, AnchorParam.Right, 50);");
            v.LayoutRect.AnchorExact(null);

            v = view2.CreateVisual(DrawRect);
            rA = v.LayoutRect;
            rA.AnchorTopLeft(null, 30, 50, 130, 40);
            v.Tag = "A";

            v = view2.CreateVisual(DrawRect);
            rB = v.LayoutRect;
            rB.AnchorTopLeft(null, 90, 50, 90, 40);
            v.Tag = "B";

            v = view2.CreateVisual(DrawVertGuideline);
            rG = v.LayoutRect;
            rG.AppendMinLeft(rA, AnchorParam.Right);
            rG.AppendMinLeft(rB, AnchorParam.Right);
            rG.AnchorVerticalLine(null);

            v = view2.CreateVisual(DrawRect);
            rC = v.LayoutRect;
            rC.AppendMinLeft(rA, AnchorParam.Right, 50);
            rC.AppendMinLeft(rB, AnchorParam.Right, 50);
            rC.SetWidth(70);
            rC.SetHeight(40);
            rC.SetVerticalCenter(null, AnchorParam.VerticalCenter);
            v.Tag = "C";

            r = view2.CreateVisual(DrawLineWithLeftArrow).LayoutRect;
            r.AnchorTopBottom(rC, 0, 0);
            r.SetLeft(rG, AnchorParam.Right);
            r.SetRight(rC, AnchorParam.Left);

            surf.Render(g);
        }

        // 図 6:比率指定の幅を含む水平方向のチェーン
        static void DrawSample3(GcGraphics g, Size _)
        {
            g.Transform = Matrix3x2.CreateScale(2);

            var surf = new Surface();

            var view = surf.CreateView(350, 120).Translate(30, 40);
            var v = view.CreateVisual(DrawView);
            v.Tag = new FigureCaption(6, "比率指定の幅を持つ 2 つの長方形と\n" +
                "その間に固定スペースを含む水平方向のチェーン:",
                "rA.SetLeft(null, AnchorParam.Left, 60);\n" +
                "rA.SetStarWidth(1);\n" +
                "rAB.SetLeftAndOpposite(rA, AnchorParam.Right);\n" +
                "rAB.SetWidth(50);\n" +
                "rB.SetLeftAndOpposite(rAB, AnchorParam.Right);\n" +
                "rB.SetStarWidth(2);\n" +
                "rB.SetRight(null, AnchorParam.Right, -60);");
            v.LayoutRect.AnchorExact(null);

            v = view.CreateVisual(DrawRect);
            var rA = v.LayoutRect;
            rA.SetHeight(40);
            rA.SetVerticalCenter(null, AnchorParam.VerticalCenter);
            v.Tag = "A";

            v = view.CreateVisual(DrawRect);
            var rB = v.LayoutRect;
            rB.SetHeight(40);
            rB.SetVerticalCenter(null, AnchorParam.VerticalCenter);
            v.Tag = "B";

            var rAB = view.CreateVisual(DrawLeftRightLines).LayoutRect;
            rAB.AnchorTopBottom(rA, 0, 0);

            // SetStarWidth メソッドは、同じチェーンに属し、
            // 比率幅を持つ他の矩形の幅に対する、
            // その特定の矩形の幅の比率を設定します。

            // SetLeftAndOpposite メソッドは、両方向で互いの位置に
            // 影響を与える矩形のチェーンを作成します。

            rA.SetLeft(null, AnchorParam.Left, 60);
            rA.SetStarWidth(1);
            rAB.SetLeftAndOpposite(rA, AnchorParam.Right);
            rAB.SetWidth(50);
            rB.SetLeftAndOpposite(rAB, AnchorParam.Right);
            rB.SetStarWidth(2);
            rB.SetRight(null, AnchorParam.Right, -60);

            var r = view.CreateVisual(DrawLineWithLeftArrow).LayoutRect;
            r.AnchorTopBottom(rA, 0, 0);
            r.SetLeft(null, AnchorParam.Left);
            r.SetRight(rA, AnchorParam.Left);

            r = view.CreateVisual(DrawLineWithRightArrow).LayoutRect;
            r.AnchorTopBottom(rB, 0, 0);
            r.SetLeft(rB, AnchorParam.Right);
            r.SetRight(null, AnchorParam.Right);

            surf.Render(g);
        }

        // 図 7:長方形と余白が均等に配置された水平方向のチェーン
        static void DrawSample4(GcGraphics g, Size _)
        {
            g.Transform = Matrix3x2.CreateScale(1.8f);

            var surf = new Surface();

            var view = surf.CreateView(430, 120).Translate(30, 40);
            var v = view.CreateVisual(DrawView);
            v.Tag = new FigureCaption(7, "余白を考慮した上で、\n" +
                "3 つの長方形が均等に配置された水平方向のチェーン:",
                "rA.SetLeft(null, AnchorParam.Left, 60);\n" +
                "rA.SetWidth(70);\n" +
                "rAB.SetLeftAndOpposite(rA, AnchorParam.Right);\n" +
                "rAB.SetStarWidth(1);\n" +
                "rAB.SetRightAndOpposite(rB, AnchorParam.Left);\n" +
                "rB.SetWidth(70);\n" +
                "rBC.SetLeftAndOpposite(rB, AnchorParam.Right);\n" +
                "rBC.SetStarWidth(1);\n" +
                "rC.SetLeftAndOpposite(rBC, AnchorParam.Right);\n" +
                "rC.SetWidth(70);\n" +
                "rC.SetRight(null, AnchorParam.Right, -60);");
            v.LayoutRect.AnchorExact(null);

            v = view.CreateVisual(DrawRect);
            var rA = v.LayoutRect;
            rA.SetHeight(40);
            rA.SetVerticalCenter(null, AnchorParam.VerticalCenter);
            v.Tag = "A";

            v = view.CreateVisual(DrawRect);
            var rB = v.LayoutRect;
            rB.SetHeight(40);
            rB.SetVerticalCenter(null, AnchorParam.VerticalCenter);
            v.Tag = "B";

            v = view.CreateVisual(DrawRect);
            var rC = v.LayoutRect;
            rC.SetHeight(40);
            rC.SetVerticalCenter(null, AnchorParam.VerticalCenter);
            v.Tag = "C";

            var rAB = view.CreateVisual(DrawLeftRightLines).LayoutRect;
            rAB.AnchorTopBottom(rA, 0, 0);

            var rBC = view.CreateVisual(DrawLeftRightLines).LayoutRect;
            rBC.AnchorTopBottom(rB, 0, 0);

            rA.SetLeft(null, AnchorParam.Left, 60);
            rA.SetWidth(70);
            rAB.SetLeftAndOpposite(rA, AnchorParam.Right);
            rAB.SetStarWidth(1);
            rAB.SetRightAndOpposite(rB, AnchorParam.Left);
            rB.SetWidth(70);
            rBC.SetLeftAndOpposite(rB, AnchorParam.Right);
            rBC.SetStarWidth(1);
            rC.SetLeftAndOpposite(rBC, AnchorParam.Right);
            rC.SetWidth(70);
            rC.SetRight(null, AnchorParam.Right, -60);

            var r = view.CreateVisual(DrawLineWithLeftArrow).LayoutRect;
            r.AnchorTopBottom(rA, 0, 0);
            r.SetLeft(null, AnchorParam.Left);
            r.SetRight(rA, AnchorParam.Left);

            r = view.CreateVisual(DrawLineWithRightArrow).LayoutRect;
            r.AnchorTopBottom(rC, 0, 0);
            r.SetLeft(rC, AnchorParam.Right);
            r.SetRight(null, AnchorParam.Right);

            surf.Render(g);
        }

        // 図 8:長方形が均等に配置された水平方向のチェーン(余白なし)
        static void DrawSample5(GcGraphics g, Size _)
        {
            g.Transform = Matrix3x2.CreateScale(2);

            var surf = new Surface();

            var view = surf.CreateView(340, 120).Translate(30, 40);
            var v = view.CreateVisual(DrawView);
            v.Tag = new FigureCaption(8, "図 7 と同じだが余白は考慮しない:",
                "rA.SetLeft(null, AnchorParam.Left);\n" +
                "rA.SetWidth(70);\n" +
                "rA.SetRightAndOpposite(rAB, AnchorParam.Left);\n" +
                "rAB.SetStarWidth(1);\n" +
                "rB.SetLeftAndOpposite(rAB, AnchorParam.Right);\n" +
                "rB.SetWidth(70);\n" +
                "rB.SetRightAndOpposite(rBC, AnchorParam.Left);\n" +
                "rBC.SetStarWidth(1);\n" +
                "rC.SetLeftAndOpposite(rBC, AnchorParam.Right);\n" +
                "rC.SetWidth(70);\n" +
                "rC.SetRight(null, AnchorParam.Right);");
            v.LayoutRect.AnchorExact(null);

            v = view.CreateVisual(DrawRect);
            var rA = v.LayoutRect;
            rA.SetHeight(40);
            rA.SetVerticalCenter(null, AnchorParam.VerticalCenter);
            v.Tag = "A";

            v = view.CreateVisual(DrawRect);
            var rB = v.LayoutRect;
            rB.SetHeight(40);
            rB.SetVerticalCenter(null, AnchorParam.VerticalCenter);
            v.Tag = "B";

            v = view.CreateVisual(DrawRect);
            var rC = v.LayoutRect;
            rC.SetHeight(40);
            rC.SetVerticalCenter(null, AnchorParam.VerticalCenter);
            v.Tag = "C";

            var rAB = view.CreateVisual(DrawLeftRightLines).LayoutRect;
            rAB.AnchorTopBottom(rA, 0, 0);

            var rBC = view.CreateVisual(DrawLeftRightLines).LayoutRect;
            rBC.AnchorTopBottom(rB, 0, 0);

            rA.SetLeft(null, AnchorParam.Left);
            rA.SetWidth(70);
            rA.SetRightAndOpposite(rAB, AnchorParam.Left);
            rAB.SetStarWidth(1);
            rB.SetLeftAndOpposite(rAB, AnchorParam.Right);
            rB.SetWidth(70);
            rB.SetRightAndOpposite(rBC, AnchorParam.Left);
            rBC.SetStarWidth(1);
            rC.SetLeftAndOpposite(rBC, AnchorParam.Right);
            rC.SetWidth(70);
            rC.SetRight(null, AnchorParam.Right);

            surf.Render(g);
        }

        // 図 9:余白なしの比率による水平方向のチェーン
        static void DrawSample6(GcGraphics g, Size _)
        {
            g.Transform = Matrix3x2.CreateScale(2);

            var surf = new Surface();

            var view = surf.CreateView(340, 120).Translate(30, 40);
            var v = view.CreateVisual(DrawView);
            v.Tag = new FigureCaption(9, "余白なしの比率による水平方向のチェーン:",
                "rA.SetLeft(null, AnchorParam.Left);\n" +
                "rA.SetStarWidth(1);\n" +
                "rA.SetRightAndOpposite(rB, AnchorParam.Left);\n" +
                "rB.SetStarWidth(2);\n" +
                "rB.SetRightAndOpposite(rC, AnchorParam.Left);\n" +
                "rC.SetStarWidth(2);\n" +
                "rC.SetRight(null, AnchorParam.Right);");
            v.LayoutRect.AnchorExact(null);

            v = view.CreateVisual(DrawRect);
            var rA = v.LayoutRect;
            rA.SetHeight(40);
            rA.SetVerticalCenter(null, AnchorParam.VerticalCenter);
            v.Tag = "A";

            v = view.CreateVisual(DrawRect);
            var rB = v.LayoutRect;
            rB.SetHeight(40);
            rB.SetVerticalCenter(null, AnchorParam.VerticalCenter);
            v.Tag = "B";

            v = view.CreateVisual(DrawRect);
            var rC = v.LayoutRect;
            rC.SetHeight(40);
            rC.SetVerticalCenter(null, AnchorParam.VerticalCenter);
            v.Tag = "C";

            rA.SetLeft(null, AnchorParam.Left);
            rA.SetStarWidth(1);
            rA.SetRightAndOpposite(rB, AnchorParam.Left);
            rB.SetStarWidth(2);
            rB.SetRightAndOpposite(rC, AnchorParam.Left);
            rC.SetStarWidth(2);
            rC.SetRight(null, AnchorParam.Right);

            surf.Render(g);
        }

        // 図 10:比率指定の余白と一纏めにされた長方形を含む水平方向のチェーン
        static void DrawSample7(GcGraphics g, Size _)
        {
            g.Transform = Matrix3x2.CreateScale(2);

            var surf = new Surface();

            var view = surf.CreateView(370, 120).Translate(30, 40);
            var v = view.CreateVisual(DrawView);
            v.Tag = new FigureCaption(10, "比率指定の余白を考慮したうえで\n長方形を一纏めにする:",
                "rBeforeA.SetLeft(null, AnchorParam.Left);\n" +
                "rBeforeA.SetStarWidth(3);\n" +
                "rA.SetLeftAndOpposite(rBeforeA, AnchorParam.Right);\n" +
                "rA.SetWidth(70);\n" +
                "rB.SetLeftAndOpposite(rA, AnchorParam.Right);\n" +
                "rB.SetWidth(70);\n" +
                "rC.SetLeftAndOpposite(rB, AnchorParam.Right);\n" +
                "rC.SetWidth(70);\n" +
                "rAfterC.SetLeftAndOpposite(rC, AnchorParam.Right);\n" +
                "rAfterC.SetStarWidth(1);\n" +
                "rAfterC.SetRight(null, AnchorParam.Right);");
            v.LayoutRect.AnchorExact(null);

            v = view.CreateVisual(DrawRect);
            var rA = v.LayoutRect;
            rA.SetHeight(40);
            rA.SetVerticalCenter(null, AnchorParam.VerticalCenter);
            v.Tag = "A";

            v = view.CreateVisual(DrawRect);
            var rB = v.LayoutRect;
            rB.SetHeight(40);
            rB.SetVerticalCenter(null, AnchorParam.VerticalCenter);
            v.Tag = "B";

            v = view.CreateVisual(DrawRect);
            var rC = v.LayoutRect;
            rC.SetHeight(40);
            rC.SetVerticalCenter(null, AnchorParam.VerticalCenter);
            v.Tag = "C";

            var rBeforeA = view.CreateVisual(DrawLineWithLeftArrow).LayoutRect;
            rBeforeA.AnchorTopBottom(rA, 0, 0);

            var rAfterC = view.CreateVisual(DrawLineWithRightArrow).LayoutRect;
            rAfterC.AnchorTopBottom(rC, 0, 0);

            rBeforeA.SetLeft(null, AnchorParam.Left);
            rBeforeA.SetStarWidth(3);
            rA.SetLeftAndOpposite(rBeforeA, AnchorParam.Right);
            rA.SetWidth(70);
            rB.SetLeftAndOpposite(rA, AnchorParam.Right);
            rB.SetWidth(70);
            rC.SetLeftAndOpposite(rB, AnchorParam.Right);
            rC.SetWidth(70);
            rAfterC.SetLeftAndOpposite(rC, AnchorParam.Right);
            rAfterC.SetStarWidth(1);
            rAfterC.SetRight(null, AnchorParam.Right);

            surf.Render(g);
        }

        // 非矩形の輪郭の中や周囲にテキストを描画
        static void DrawSample8(GcGraphics g, Size pageSize)
        {
            g.FillRectangle(new RectangleF(0, 0, pageSize.Width, pageSize.Height), Color.White);

            var surf = new Surface();

            // Contour オブジェクトは、テキスト矩形に定義された制約で参照することができます。
            CreateFigure1(surf, out Contour c1_outer);
            CreateFigure2(surf, out Contour c2_outer, out Contour c2_inner);

            const float rowHeight = 24f;
            const float lineSpacing = 5f;
            const float paragraphSpacing = 10f;
            const float fontSize = 18f;

            // 横書きテキストを表示するメインの View オブジェクトです。
            var view = surf.CreateView(pageSize.Width, pageSize.Height);
            var rcMargin = view.CreateSpace().LayoutRect;
            rcMargin.AnchorDeflate(null, 20);

            var tf = new TextFormat
            {
                Font = GCTEXT.Font.FromFile(Path.Combine("Resources", "Fonts", "calibri.ttf")),
                FontSizeInGraphicUnits = true,
                FontSize = fontSize,
                FontFeatures = new FontFeature[] { new FontFeature(FeatureTag.liga, false) }
            };
            var noRects = new List<ObjectRect>();

            TextLayout tl = null;

            var tso = new TextSplitOptions
            {
                RestObjectRects = noRects,
                AllowMovingAllToRest = true
            };

            LayoutRect rcPrevTop = null;
            LayoutRect rcPrevLeft = null;
            bool paragraphStarted = false;

            while (true)
            {
                var r0 = view.CreateVisual(DrawTextFragment).LayoutRect;
                if (rcPrevLeft != null)
                    r0.SetTop(rcPrevLeft, AnchorParam.Top);
                else if (rcPrevTop != null)
                    r0.SetTop(rcPrevTop, AnchorParam.Bottom, paragraphStarted ? lineSpacing : paragraphSpacing);
                else
                    r0.SetTop(rcMargin, AnchorParam.Top);
                if (rcPrevLeft is null)
                    r0.SetLeft(rcMargin, AnchorParam.Left);
                else
                    r0.SetLeft(rcPrevLeft, AnchorParam.Right);

                r0.SetHeight(rowHeight);
                r0.AppendMaxRight(rcMargin, AnchorParam.Right);

                var r1 = view.CreateSpace().LayoutRect;
                r1.SetTop(r0, AnchorParam.Top);
                r1.SetHeight(rowHeight);
                r1.SetLeft(r0, AnchorParam.Right);
                r1.AppendMaxRight(rcMargin, AnchorParam.Right);

                r0.AppendMaxRight(c1_outer, ContourPosition.FirstInOutside);
                r0.AppendMaxRight(c2_outer, ContourPosition.FirstInOutside);

                r1.AppendMaxRight(c1_outer, ContourPosition.NextOutOutside);
                r1.AppendMaxRight(c2_outer, ContourPosition.NextOutOutside);

                surf.PerformLayout();

                if (!paragraphStarted)
                {
                    // ObjectRects プロパティに null でない値(空のリストが有効)を設定し、
                    // AllowOverhangingWords プロパティに true を設定することが重要です。

                    tl = g.CreateTextLayout();
                    tl.ObjectRects = noRects;
                    tl.TextAlignment = TextAlignment.Justified;
                    tl.AllowOverhangingWords = true;
                    tl.FirstLineIndent = 40f;
                    tl.JustifiedTextExtension = 0.1f;

                    tl.Append(Common.Util.LoremIpsum(1), tf);

                    tl.MaxWidth = r0.Width;
                    tl.MaxHeight = r0.Height;
                    ((Visual)r0.Tag).Tag = tl;

                    if (!tl.PerformLayout())
                    {
                        paragraphStarted = true;
                    }
                }
                else
                {
                    tso.RestMaxWidth = r0.Width;
                    tso.RestMaxHeight = r0.Height;
                    var res = tl.Split(tso, out TextLayout rest);
                    if (res != SplitResult.CannotSplit)
                    {
                        tl = rest;
                        ((Visual)r0.Tag).Tag = rest;
                        if (tl.PerformLayout())
                        {
                            paragraphStarted = false;
                        }
                    }
                }

                if (paragraphStarted && r1.Width > 0f)
                    rcPrevLeft = r1;
                else
                {
                    ((Space)r1.Tag).Detach();
                    var spacing = paragraphStarted ? lineSpacing : paragraphSpacing;
                    if (rcMargin.P2Y - r0.P2Y < spacing + rowHeight)
                    {
                        break;
                    }
                    rcPrevLeft = null;
                    rcPrevTop = r0;
                }
            }

            tl.Truncate(TrimmingGranularity.Word);

            // 楕円の中にテキストを表示する2番目の View です。
            var view2 = surf.CreateView(pageSize.Width / 3, pageSize.Height / 2).Translate(520, 260).Rotate(15);

            tf = new TextFormat(tf)
            {
                ForeColor = Color.Purple,
                FontSize = 14,
            };
            tl = g.CreateTextLayout();
            tl.ObjectRects = noRects;
            tl.TextAlignment = TextAlignment.Center;
            tl.AllowOverhangingWords = true;

            tl.Append(Common.Util.LoremIpsum(1), tf);

            rcPrevTop = null;
            paragraphStarted = false;

            while (true)
            {
                var r0 = view2.CreateSpace().LayoutRect;
                if (rcPrevTop != null)
                    r0.SetTop(rcPrevTop, AnchorParam.Bottom, 4);
                else
                    r0.SetTop(null, AnchorParam.Top);
                r0.SetLeft(null, AnchorParam.Left);

                r0.SetHeight(18);
                r0.AppendMaxRight(null, AnchorParam.Right);

                var r1 = view2.CreateVisual(DrawTextFragment).LayoutRect;
                r1.SetTop(r0, AnchorParam.Top);
                r1.SetHeight(18);
                r1.SetLeft(r0, AnchorParam.Right);
                r1.AppendMaxRight(null, AnchorParam.Right);

                r0.AppendMaxRight(c2_inner, ContourPosition.FirstInInside);
                r1.AppendMaxRight(c2_inner, ContourPosition.NextOutInside);

                surf.PerformLayout();
                if (r1.Width == 0f)
                {
                    ((Space)r1.Tag).Detach();
                    if (paragraphStarted)
                    {
                        break;
                    }
                }
                else if (!paragraphStarted)
                {
                    tl.MaxWidth = r1.Width;
                    tl.MaxHeight = r1.Height;
                    ((Visual)r1.Tag).Tag = tl;
                    if (tl.PerformLayout())
                    {
                        break;
                    }
                    paragraphStarted = true;
                }
                else
                {
                    tso.RestMaxWidth = r1.Width;
                    tso.RestMaxHeight = r1.Height;
                    var res = tl.Split(tso, out TextLayout rest);
                    if (res == SplitResult.FitAll)
                    {
                        ((Space)r1.Tag).Detach();
                        break;
                    }
                    if (res == SplitResult.Split)
                    {
                        ((Visual)r1.Tag).Tag = rest;
                        tl = rest;
                    }
                }
                rcPrevTop = r0;
            }

            tl.Truncate(TrimmingGranularity.Word);

            surf.Render(g);
        }

        //
        // 共通のユーティリティクラスとメソッド
        //

        class FigureCaption
        {
            public FigureCaption(int number, string description, string code = null)
            {
                Number = number;
                Description = description;
                Code = code;
            }
            public int Number { get; }
            public string Description { get; }
            public string Code { get; }
        }

        static void DrawTextFragment(GcGraphics g, Visual v)
        {
            if (v.Tag is TextLayout tl)
            {
                g.DrawTextLayout(tl, new PointF(0, 0));
            }
        }

        static void CreateFigure1(Surface surf, out Contour c_outer)
        {
            // 黄色い多角形の View です。
            var view = surf.CreateView(0, 0).Translate(120, -40).Rotate(40);
            var lv = view.LayoutView;

            var c = lv.CreateContour();

            var v = view.CreateVisual(c, true, (g, v) =>
            {
                g.FillPolygon(v.Points, Color.LemonChiffon);
                g.DrawPolygon(v.Points, Color.Green, 3);
            });
            var rect = v.LayoutRect;
            rect.AnchorTopLeft(null, 100, 150, 170, 70);

            c.AddPoints(new AnchorPoint[]
            {
                rect.CreatePoint(0, 0, -20, -20),
                rect.CreatePoint(1, 0, 20, -20),
                rect.CreatePoint(1, 0, 20, -120),
                rect.CreatePoint(1, 0, 120, -120),
                rect.CreatePoint(1, 1, 120, 20),
                rect.CreatePoint(0, 1, -20, 20)
            });

            surf.PerformLayout();

            var points = c.MapToView(lv);

            // Contour から外側のオフセットを作るために、Contour を
            // GraphicsPath に変換し、太いペンにて Widen メソッドを適用します。
            // 出来上がった図形は、外側の Contour の作成に使用されます。
            var gp = new GraphicsPath(new FreeFormPolygon(points));
            var gp2 = gp.Widen(new GCDRAW.Pen(Color.White, 7 * 2));

            var fig_outer = gp2.Figures[0];

            fig_outer.Flatten();
            var outer_points = fig_outer.TransformedPoints;

            c_outer = lv.CreateContour();
            for (int i = 0; i < outer_points.Length; i++)
            {
                var p = outer_points[i];
                c_outer.AddPoint(lv.CreatePoint(0f, 0f, p.X, p.Y));
            }
        }

        static void CreateFigure2(Surface surf, out Contour c_outer, out Contour c_inner)
        {
            // 回転した楕円の View です。
            var view = surf.CreateView(360, 150).Translate(450, 530).Rotate(-40);
            var lv = view.LayoutView;

            var c = lv.CreateContour();

            view.CreateVisual(c, false, (g, v) =>
            {
                g.DrawPolygon(v.Points, Color.DeepPink, 3);
            });

            // 楕円の内側と外側のオフセットを作るために、楕円の図形を点の配列に変換し、
            // GraphicsPath を作成するために使用します。
            // そして、太いペンにて GraphicsPath.Widen メソッドを呼び出します。
            // 出来上がった外側と内側の図形は、後で制約に使用するために
            // 新しい Contour に変換することができます。
            IFigure ef = new EllipticFigure(lv.AsRectF());
            ef.Flatten();
            var points = ef.TransformedPoints;
            int count = points.Length;
            var list = new List<AnchorPoint>(count);
            for (int i = 0; i < count; i++)
            {
                var p = points[i];
                list.Add(lv.CreatePoint(0, 0, p.X, p.Y));
            }
            c.AddPoints(list);

            var gp = new GraphicsPath(ef);
            var gp2 = gp.Widen(new GCDRAW.Pen(Color.White, 7 * 2));

            var fig_outer = gp2.Figures[0];
            var fig_inner = gp2.Figures[1];

            fig_outer.Flatten();
            var outer_points = fig_outer.TransformedPoints;

            c_outer = lv.CreateContour();
            for (int i = 0; i < outer_points.Length; i++)
            {
                var p = outer_points[i];
                c_outer.AddPoint(lv.CreatePoint(0f, 0f, p.X, p.Y));
            }

            fig_inner.Flatten();
            var inner_points = fig_inner.TransformedPoints;

            c_inner = lv.CreateContour();
            for (int i = 0; i < inner_points.Length; i++)
            {
                var p = inner_points[i];
                c_inner.AddPoint(lv.CreatePoint(0f, 0f, p.X, p.Y));
            }
        }

        static readonly TextFormat FormatBox = new TextFormat()
        {
            Font = GCTEXT.Font.FromFile(Path.Combine("Resources", "Fonts", "segoeui.ttf")),
            FontSize = 14,
            FontSizeInGraphicUnits = true,
            ForeColor = BoxColor
        };

        static readonly TextFormat FormatDesc = new TextFormat(FormatBox)
        {
            FontSize = 12,
            ForeColor = DescColor
        };

        static readonly TextFormat FormatCaption = new TextFormat(FormatDesc)
        {
            FontBold = true
        };

        static readonly TextFormat FormatCode = new TextFormat(FormatDesc)
        {
            Font = GCTEXT.Font.FromFile(Path.Combine("Resources", "Fonts", "cour.ttf")),
            ForeColor = CodeColor
        };

        static void DrawView(GcGraphics g, Visual v)
        {
            var rect = v.AsRectF();
            g.FillRectangle(rect, Color.FromArgb(31, 82, 100));
            g.DrawRectangle(rect, new GCDRAW.Pen(Color.FromArgb(200, 200, 200), 1)
            {
                DashPattern = new float[] { 7, 3 }
            });
            if (v.Tag is FigureCaption fc)
            {
                var tl = g.CreateTextLayout();
                tl.Append($"図 {fc.Number}. ", FormatCaption);
                tl.AppendLine(fc.Description, FormatDesc);
                if (fc.Code != null)
                {
                    tl.Append(fc.Code, FormatCode);
                }
                g.DrawTextLayout(tl, new PointF(0, v.Height + 5));
            }
        }

        static void DrawVertGuideline(GcGraphics g, Visual v)
        {
            g.DrawLine(new PointF(0, 0), new PointF(0, v.Height), new GCDRAW.Pen(BoxColor, 1)
            {
                DashPattern = new float[] { 1, 2 }
            });
            if (v.Tag is string s)
            {
                var tl = g.CreateTextLayout();
                tl.Append(s, FormatDesc);
                tl.PerformLayout();
                var rect = tl.ContentRectangle;
                rect.X = -rect.Width * 0.5f;
                rect.Y = 10;
                rect.Inflate(3, 1);
                g.FillRoundRect(rect, 3, RectColor);
                g.DrawTextLayout(tl, new PointF(rect.X + 3, rect.Y + 1));
            }
        }

        static void DrawRect(GcGraphics g, Visual v)
        {
            var rect = v.AsRectF();
            g.FillRectangle(rect, RectColor);
            g.DrawRectangle(rect, new GCDRAW.Pen(BoxColor, 1));
            if (v.Tag is string s)
            {
                var tl = g.CreateTextLayout();
                tl.MaxWidth = v.Width;
                tl.MaxHeight = v.Height;
                tl.TextAlignment = TextAlignment.Center;
                tl.ParagraphAlignment = ParagraphAlignment.Center;
                tl.Append(s, FormatBox);
                g.DrawTextLayout(tl, new PointF(0, 0));
            }
        }

        static void DrawLineWithLeftArrow(GcGraphics g, Visual v)
        {
            var y = v.Height * 0.5f;
            g.DrawLine(new PointF(5, y), new PointF(v.Width, y), new GCDRAW.Pen(BoxColor, 0.7f));
            DrawLeftArrow(g, 0, y);
        }

        static void DrawLineWithRightArrow(GcGraphics g, Visual v)
        {
            var y = v.Height * 0.5f;
            g.DrawLine(new PointF(0, y), new PointF(v.Width - 5, y), new GCDRAW.Pen(BoxColor, 0.7f));
            DrawRightArrow(g, v.Width, y);
        }

        static void DrawLeftRightLines(GcGraphics g, Visual v)
        {
            var y = v.Height * 0.33f;
            g.DrawLine(new PointF(5, y), new PointF(v.Width, y), new GCDRAW.Pen(BoxColor, 0.7f));
            DrawLeftArrow(g, 0, y);
            y = v.Height * 0.66f;
            g.DrawLine(new PointF(0, y), new PointF(v.Width - 5, y), new GCDRAW.Pen(BoxColor, 0.7f));
            DrawRightArrow(g, v.Width, y);
        }

        static void DrawVertLineWithLeftArrow(GcGraphics g, Visual v)
        {
            var x = v.Width * 0.5f;
            g.DrawLines(new PointF[]
            {
                new PointF(5, 0),
                new PointF(x, 0),
                new PointF(x, v.Height),
                new PointF(v.Width, v.Height)
            }, new GCDRAW.Pen(BoxColor, 0.7f));
            DrawLeftArrow(g, 0, 0);
        }

        static void DrawLineWithUpArrow(GcGraphics g, Visual v)
        {
            var x = v.Width * 0.5f;
            g.DrawLine(new PointF(x, 5), new PointF(x, v.Height), new GCDRAW.Pen(BoxColor, 0.7f));
            DrawUpArrow(g, x, 0);
        }

        static void DrawLeftArrow(GcGraphics g, float x, float y)
        {
            var pts = new PointF[]
            {
                new PointF(x, y),
                new PointF(x + 8, y - 2.5f),
                new PointF(x + 8, y + 2.5f),
            };
            g.FillPolygon(pts, BoxColor);
        }

        static void DrawRightArrow(GcGraphics g, float x, float y)
        {
            var pts = new PointF[]
            {
                new PointF(x, y),
                new PointF(x - 8, y + 2.5f),
                new PointF(x - 8, y - 2.5f),
            };
            g.FillPolygon(pts, BoxColor);
        }

        static void DrawUpArrow(GcGraphics g, float x, float y)
        {
            var pts = new PointF[]
            {
                new PointF(x, y),
                new PointF(x + 2.5f, y + 8),
                new PointF(x - 2.5f, y + 8),
            };
            g.FillPolygon(pts, BoxColor);
        }
    }
}