LongTable.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.Linq;
using System.Collections.Generic;
using GrapeCity.Documents.Pdf;
using GrapeCity.Documents.Text;
using GrapeCity.Documents.Layout;
using GrapeCity.Documents.Layout.Composition;
using GCTEXT = GrapeCity.Documents.Text;
using GCDRAW = GrapeCity.Documents.Drawing;

namespace DsPdfWeb.Demos
{
    // This demo creates a table with variable row height and spanning many pages,
    // using classes from the GrapeCity.Documents.Layout.Composition namespace.
    public class LongTable
    {
        string[] _headers;
        TextFormat _thFormat;
        TextFormat _tdFormat;
        float _resolution;

        readonly List<LayoutRect> _rows = new();
        readonly List<LayoutRect> _columns = new();
        readonly List<Visual> _cells = new();
        readonly List<TextLayout> _texts = new();

        GcPdfGraphics _g;
        View _pageView;
        Layer _textLayer;
        LayoutRect _bounds;
        Surface _sf;

        public int CreatePDF(Stream stream)
        {
            var doc = new GcPdfDocument();

            var page = doc.NewPage();
            var pageSize = page.Size;
            _g = page.Graphics;

            _sf = new Surface();
            _pageView = _sf.CreateView(pageSize.Width, pageSize.Height);
            _textLayer = _pageView.CreateSubLayer();
            _resolution = _g.Resolution;

            var marginsVisual = _pageView.CreateVisual((g, v) =>
            {
                g.FillRectangle(v.AsRectF(), Color.FromArgb(0xFF, 0xF8, 0xF8));
            });
            _bounds = marginsVisual.LayoutRect;
            _bounds.AnchorInflate(null, -_resolution);
            _sf.PerformLayout();

            var noteBounds = AddNote("We create a table with a large number of variable height rows. " +
                "The table starts at a specified position on the first page, and spans multiple pages.");

            var fc = FontCollection.SystemFonts;
            _thFormat = new TextFormat
            {
                Font = fc.FindFamilyName("Trebuchet MS", true) ?? fc.FindFamilyName("Arial", true),
                FontBold = true,
                ForeColor = Color.White
            };
            _tdFormat = new TextFormat
            {
                Font = fc.FindFamilyName("Trebuchet MS") ?? fc.FindFamilyName("Arial")
            };

            _headers = new string[] { "#", "Lorem", "Ipsum" };
            CreateColumns();

            var headerRowRect = CreateHeaderRow();
            headerRowRect.SetTop(noteBounds, AnchorParam.Bottom, _resolution / 2);
            FillHeaderRow(headerRowRect);

            var prevRowRect = headerRowRect;
            var numberOfRows = Common.Util.NewRandom().Next(100, 500);

            for (int i = 0; i < numberOfRows; i++)
            {
                var row = CreateRow((i & 1) != 0);
                var rc = row.LayoutRect;
                rc.SetTop(prevRowRect, AnchorParam.Bottom);
                if (!FillRow(i, rc))
                {
                    row.Detach();
                    CreateTableGrid();
                    _sf.Render(_g);

                    _columns.Clear();
                    _rows.Clear();

                    page = doc.NewPage();
                    pageSize = page.Size;
                    _g = page.Graphics;

                    _sf = new Surface();
                    _pageView = _sf.CreateView(pageSize.Width, pageSize.Height);
                    _textLayer = _pageView.CreateSubLayer();

                    marginsVisual = _pageView.CreateVisual((g, v) =>
                    {
                        g.FillRectangle(v.AsRectF(), Color.FromArgb(0xFF, 0xF8, 0xF8));
                    });
                    _bounds = marginsVisual.LayoutRect;
                    _bounds.AnchorInflate(null, -_resolution);
                    _sf.PerformLayout();

                    CreateColumns();
                    headerRowRect = CreateHeaderRow();
                    headerRowRect.SetTop(_bounds, AnchorParam.Top);
                    FillHeaderRow(headerRowRect);

                    row = CreateRow((i & 1) != 0);
                    rc = row.LayoutRect;
                    rc.SetTop(headerRowRect, AnchorParam.Bottom);

                    RestoreRow(rc);
                }
                prevRowRect = rc;
            }

            CreateTableGrid();
            _sf.Render(_g);

            doc.Save(stream);
            return doc.Pages.Count;
        }

        class TableGridInfo
        {
            public TableGridInfo(LayoutRect[] columns, LayoutRect[] rows)
            {
                Columns = columns;
                Rows = rows;
            }
            public readonly LayoutRect[] Columns;
            public readonly LayoutRect[] Rows;
        }

        void CreateTableGrid()
        {
            var grid = _pageView.CreateVisual(false);
            grid.Tag = new TableGridInfo(_columns.ToArray(), _rows.ToArray());
            grid.Draw = (g, v) =>
            {
                var pen = new GCDRAW::Pen(Color.FromArgb(0xDD, 0xDD, 0xDD), 1);
                var tgi = (TableGridInfo)v.Tag;
                int lastColumnIndex = tgi.Columns.Length - 1;
                int lastRowIndex = tgi.Rows.Length - 1;

                float xMin = tgi.Columns[0].P0X;
                float yMin = tgi.Rows[0].P0Y;
                float xMax = tgi.Columns[lastColumnIndex].P1X;
                float yMax = tgi.Rows[lastRowIndex].P2Y;

                g.DrawRectangle(new RectangleF(xMin, yMin, xMax - xMin, yMax - yMin), pen);
                for (int i = 0; i < lastColumnIndex; i++)
                {
                    float x = tgi.Columns[i].P1X;
                    g.DrawLine(new PointF(x, yMin), new PointF(x, yMax), pen);
                }
                for (int i = 0; i < lastRowIndex; i++)
                {
                    float y = tgi.Rows[i].P2Y;
                    g.DrawLine(new PointF(xMin, y), new PointF(xMax, y), pen);
                }
            };
        }

        void CreateColumns()
        {
            LayoutRect column1 = _pageView.CreateSpace().LayoutRect;
            LayoutRect column2 = _pageView.CreateSpace().LayoutRect;
            LayoutRect column3 = _pageView.CreateSpace().LayoutRect;

            column1.AnchorTopBottom(_bounds, 0, 0);
            column2.AnchorTopBottom(_bounds, 0, 0);
            column3.AnchorTopBottom(_bounds, 0, 0);

            column1.SetLeft(_bounds, AnchorParam.Left);
            column2.SetLeftAndOpposite(column1, AnchorParam.Right);
            column3.SetLeftAndOpposite(column2, AnchorParam.Right);
            column3.SetRight(_bounds, AnchorParam.Right);

            column1.SetStarWidth(1);
            column2.SetStarWidth(3);
            column3.SetStarWidth(3);

            _sf.PerformLayout();

            _columns.Add(column1);
            _columns.Add(column2);
            _columns.Add(column3);
        }

        LayoutRect CreateHeaderRow()
        {
            var rc = _pageView.CreateVisual((g, v) =>
            {
                g.FillRectangle(v.AsRectF(), Color.FromArgb(0x33, 0x77, 0xFF));
            }).LayoutRect;
            rc.AnchorLeftRight(_bounds, 0, 0);
            return rc;
        }

        void FillHeaderRow(LayoutRect headerRowRect)
        {
            for (int i = 0; i < _columns.Count; i++)
            {
                var column = _columns[i];
                var tl = new TextLayout(_resolution)
                {
                    MaxWidth = column.Width,
                    TextAlignment = TextAlignment.Center,
                    MarginLeft = 8,
                    MarginRight = 8,
                    MarginTop = 12
                };
                tl.Append(_headers[i], _thFormat);
                tl.PerformLayout();

                var cell = _textLayer.CreateVisual();
                cell.Tag = tl;
                cell.Draw = (g, v) =>
                {
                    g.DrawTextLayout((TextLayout)v.Tag, new PointF(0, 0));
                };
                var rc = cell.LayoutRect;
                rc.AnchorLeftRight(column, 0, 0);
                rc.SetTop(headerRowRect, AnchorParam.Top, 0);
                rc.SetHeight(tl.ContentHeight + 12 + 12);
                headerRowRect.AppendMinBottom(rc, AnchorParam.Bottom);
            }
            _rows.Add(headerRowRect);
            _sf.PerformLayout();
        }

        Visual CreateRow(bool even)
        {
            var row = _pageView.CreateVisual();
            if (even)
                row.Draw = (g, v) => g.FillRectangle(v.AsRectF(), Color.FromArgb(0xF2, 0xF2, 0xF2));
            else
                row.Draw = (g, v) => g.FillRectangle(v.AsRectF(), Color.White);
            row.LayoutRect.AnchorLeftRight(_bounds, 0, 0);
            return row;
        }

        bool FillRow(int index, LayoutRect rowRect)
        {
            _cells.Clear();
            _texts.Clear();
            for (int i = 0; i < _columns.Count; i++)
            {
                var column = _columns[i];
                var tl = new TextLayout(_resolution)
                {
                    MaxWidth = column.Width,
                    MarginAll = 8
                };
                if (i == 0)
                {
                    tl.TextAlignment = TextAlignment.Center;
                    tl.Append((index + 1).ToString(), _tdFormat);
                }
                else
                {
                    // Add random data:
                    tl.Append(Common.Util.LoremIpsum(1, 1, 2, 1, 15), _tdFormat);
                }
                tl.PerformLayout();
                _texts.Add(tl);

                var cell = _textLayer.CreateVisual();
                cell.Tag = tl;
                cell.Draw = (g, v) =>
                {
                    g.DrawTextLayout((TextLayout)v.Tag, new PointF(0, 0));
                };
                var rc = cell.LayoutRect;
                rc.AnchorLeftRight(column, 0, 0);
                rc.SetTop(rowRect, AnchorParam.Top, 0);
                rc.SetHeight(tl.ContentHeight + 8 + 8);
                rowRect.AppendMinBottom(rc, AnchorParam.Bottom);
                _cells.Add(cell);
            }
            _sf.PerformLayout();

            if (rowRect.P2Y > _bounds.P2Y)
            {
                for (int i = 0; i < _cells.Count; i++)
                {
                    _cells[i].Detach();
                }
                _cells.Clear();
                return false;
            }

            _rows.Add(rowRect);
            return true;
        }

        void RestoreRow(LayoutRect rowRect)
        {
            for (int i = 0; i < _columns.Count; i++)
            {
                var tl = _texts[i];
                var cell = _textLayer.CreateVisual();
                cell.Tag = tl;
                cell.Draw = (g, v) =>
                {
                    g.DrawTextLayout((TextLayout)v.Tag, new PointF(0, 0));
                };
                var rc = cell.LayoutRect;
                rc.AnchorLeftRight(_columns[i], 0, 0);
                rc.SetTop(rowRect, AnchorParam.Top, 0);
                rc.SetHeight(tl.ContentHeight + 8 + 8);
                rowRect.AppendMinBottom(rc, AnchorParam.Bottom);
            }
            _sf.PerformLayout();
            _rows.Add(rowRect);
        }

        LayoutRect AddNote(string text)
        {
            var pad = _resolution / 8f;
            var tl = new TextLayout(_resolution)
            {
                MaxWidth = _bounds.Width,
                MaxHeight = _bounds.Height,
                MarginAll = pad
            };
            tl.Append(text, new TextFormat { Font = StandardFonts.Helvetica });
            tl.PerformLayout();

            var noteVisual = _pageView.CreateVisual();
            noteVisual.Tag = tl;
            noteVisual.Draw = (g, v) =>
            {
                var rect = v.AsRectF();
                g.FillRectangle(rect, Color.FromArgb(213, 221, 240));
                g.DrawRectangle(rect, Color.FromArgb(59, 92, 170), 0.5f);
                g.DrawTextLayout((TextLayout)v.Tag, new PointF(0, 0));
            };

            var textRect = tl.ContentRectangle;
            textRect.Inflate(pad, pad);
            var rc = noteVisual.LayoutRect;
            rc.AnchorTopLeft(_bounds, 0, 0, textRect.Width, textRect.Height);
            return rc;
        }
    }
}