LayoutDemos.cs
//
// This code is part of Document Solutions for PDF demos.
// Copyright (c) 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
{
    // This integrated demo shows how to use helper classes
    // in the GrapeCity.Documents.Layout.Composition namespace
    // to create complex and flexible constraint-based layouts
    // with custom z-order and clipping.
    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($"Unknown parameterized sample: {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());

            // Save the 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($"Unknown parameterized sample: {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[]>()
            {
                // Strings are name, description, info. Rest are arbitrary strings:
                new string[] { "Figures 1-4", "Figures 1-4: horizontal, vertical, alignment constraint and guidelines",
                    null,
                    "DrawSample1" },
                new string[] { "Figure 5", "Figure 5: Barrier constraint anchored to two visuals",
                    null,
                    "DrawSample2" },
                new string[] { "Figure 6", "Figure 6: Horizontal chain with weighted widths",
                    null,
                    "DrawSample3" },
                new string[] { "Figure 7", "Figure 7: Horizontal chain with evenly distributed rectangles and margins",
                    null,
                    "DrawSample4" },
                new string[] { "Figure 8", "Figure 8: Horizontal chain with evenly distributed rectangles, no margins",
                    null,
                    "DrawSample5" },
                new string[] { "Figure 9", "Figure 9: Weighted horizontal chain without margins",
                    null,
                    "DrawSample6" },
                new string[] { "Figure 10", "Figure 10: Horizontal chain with weighted margins and packed rectangles",
                    null,
                    "DrawSample7" },
                new string[] { "Text Flow", "Text flow in and around non-rectangular contours",
                    null,
                    "DrawSample8" },
            };
        }

        // Figures 1-4: horizontal, vertical, alignment constraint and guidelines.
        static void DrawSample1(GcGraphics g, Size _)
        {
            g.Transform = Matrix3x2.CreateScale(1.4f);

            // The Surface object can layout Visuals and draw them on the graphics.
            var surf = new Surface();

            // view1

            // The View object represents a group of Visuals with a transformation matrix.
            var view1 = surf.CreateView(250, 150).Translate(50, 40);

            // Visual is an element with the associated LayoutRect used for its
            // positioning/ and a delegate that draws its content on the graphics.
            var v = view1.CreateVisual(DrawView);
            v.Tag = new FigureCaption(1, "A horizontal constraint to the parent:",
                "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, "An offset horizontal alignment constraint:",
                "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, "Horizontal and vertical constraints:",
                "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, "A rectangle constrained to a guideline:",
                "var anchorPoint = layoutView.CreatePoint(0.25f, 0);\n" +
                "rA.SetLeft(anchorPoint, 60);");
            v.LayoutRect.AnchorExact(null);

            // An anchor point can work as the guideline on the X or the Y axes.
            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);
        }

        // Figure 5: Barrier constraint anchored to two visuals.
        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;

            // Adding multiple MinLeft constraints to the same rectangle creates
            // a barrier to be used as the base for other rectangles.
            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 is constrained to a barrier, which moves based on\n" +
                "the position and size of both A and 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);
        }

        // Figure 6: Horizontal chain with weighted widths.
        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, "A horizontal chain with two rectangles having weighted\nwidths and a fixed space between them:",
                "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);

            // The SetStarWidth method sets the weight of width of that specific
            // rectangle relative to the width of other rectangles that belong
            // to the same chain and have the "star" width.

            // SetLeftAndOpposite method makes a chain of rectangles that affect
            // the position of each other in both directions.

            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);
        }

        // Figure 7: Horizontal chain with evenly distributed rectangles and margins.
        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, "A horizontal chain with three evenly distributed rectangles\nafter margins are accounted for:",
                "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);
        }

        // Figure 8: Horizontal chain with evenly distributed rectangles, no margins.
        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, "Same as Figure 7, without accounting for the margins:",
                "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);
        }

        // Figure 9: Weighted horizontal chain without margins.
        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, "A weighted horizontal chain without margins:",
                "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);
        }

        // Figure 10: Horizontal chain with weighted margins and packed rectangles.
        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, "Rectangles are packed together after margins with weights\nare accounted for:",
                "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);
        }

        // Text flow in and around non-rectangular contours.
        static void DrawSample8(GcGraphics g, Size pageSize)
        {
            g.FillRectangle(new RectangleF(0, 0, pageSize.Width, pageSize.Height), Color.White);

            var surf = new Surface();

            // Contour objects can be referenced in constraints defined for text rectangles.
            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;

            // The main View object that displays horizontal text.
            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)
                {
                    // It is important to have the ObjectRects property set
                    // to a not null value (an empty list works well) and
                    // the AllowOverhangingWords property set to 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);

            // The second View showing text in the ellipse.
            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);
        }

        //
        // Common utility classes and methods.
        //

        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)
        {
            // A View for the yellow polygon.
            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);

            // To make the outer offset from the Contour we convert
            // the Contour to a GraphicsPath, then apply the Widen
            // method with a thick pen. The resulting figure is
            // used for creating the outer 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)
        {
            // A View for the rotated ellipse.
            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);
            });

            // To make the inner and the outer offsets from the ellipse
            // we convert the elliptic figure to an array of points which
            // is used later for creating a GraphicsPath. Then, we call
            // the GraphicsPath.Widen method with a thick pen.
            // The resulting outer and inner figures can be converted
            // into new contours for later use in constraints.
            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($"Figure {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);
        }
    }
}