TagTextLayout.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 GrapeCity.Documents.Pdf;
using GrapeCity.Documents.Text;
using GrapeCity.Documents.Pdf.Structure;
using GrapeCity.Documents.Pdf.MarkedContent;

namespace DsPdfWeb.Demos.Basics
{
    // This sample shows how to create tagged (structured) PDF and attach
    // tags to individual paragraphs in a TextLayout that is used to render
    // them together, splitting between pages.
    // The code generating the document is similar to that used in PaginatedText,
    // but adds tags.
    // To see/explore the tags, open the document in Adobe Acrobat Pro and go to
    // View | Navigation Panels | Tags.
    public class TagTextLayout
    {
        public int CreatePDF(Stream stream)
        {
            var doc = new GcPdfDocument();

            // Create a Part element, it will contain P (paragraph) elements:
            var sePart = new StructElement("Part");
            doc.StructTreeRoot.Children.Add(sePart);

            // Create and set up a TextLayout to render paragraphs:
            var tl = new TextLayout(72);
            tl.DefaultFormat.Font = StandardFonts.Times;
            tl.DefaultFormat.FontSize = 12;
            tl.FirstLineIndent = 72 / 2;
            tl.MaxWidth = doc.PageSize.Width;
            tl.MaxHeight = doc.PageSize.Height;
            tl.MarginAll = tl.Resolution;
            //
            // Append the text (20 paragraphs so they would not fit on a single page)
            // (note that TextLayout interprets "\r\n" as paragraph delimiter):
            //
            // Get the text (20 paragraphs):
            var text = Common.Util.LoremIpsum(20);
            // In order to tag the individual paragraphs, we need to split the text into paragraphs,
            // and use each paragraph format's Tag property (which is not related to PDF tags, 
            // it is just an arbitrary data that can be associated with a TextFormat) to add the
            // paragraph's index to the paragraph:
            var pars = text.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
            for (int i = 0; i < pars.Length; ++i)
            {
                var tf = new TextFormat(tl.DefaultFormat) { Tag = i };
                tl.AppendLine(pars[i], tf);
            }

            // Layout the text:
            tl.PerformLayout(true);
            // Use split options to provide widow/orphan control:
            var to = new TextSplitOptions(tl)
            {
                MinLinesInFirstParagraph = 2,
                MinLinesInLastParagraph = 2,
            };
            // TextLayoutHandler implements ITextLayoutHandler, which
            // allows tagging the text as it is rendered:
            var tlh = new TextLayoutHandler() { ParentElement = sePart };

            // In a loop, split and render the text:
            while (true)
            {
                // 'rest' will accept the text that did not fit:
                var splitResult = tl.Split(to, out TextLayout rest);
                var page = doc.Pages.Add();
                var g = page.Graphics;
                // Tell the TextLayoutHandler which page we're on:
                tlh.Page = page;
                // ..and associate it with the graphics:
                g.TextLayoutHandler = tlh;
                // Draw the text that fits on the current page, and advance to next page unless we're done:
                g.DrawTextLayout(tl, PointF.Empty);
                if (splitResult != SplitResult.Split)
                    break;
                tl = rest;
            }
            // Mark document as tagged:
            doc.MarkInfo.Marked = true;

            // Done:
            doc.Save(stream);
            return doc.Pages.Count;
        }

        // Custom class that allows tagging content as it is rendered by TextLayout:
        private class TextLayoutHandler : ITextLayoutHandler
        {
            private int _tagIndex;
            private int _currentParagraphIndex = -1;
            private StructElement _currentparagraphElement;
            public StructElement ParentElement;
            public Page Page;

            public void TextTagBegin(GcPdfGraphics graphics, TextLayout textLayout, object tag)
            {
                int paragraphIndex;
                if (tag is int)
                    paragraphIndex = (int)tag;
                else
                    paragraphIndex = -1;

                StructElement paragraphElement;
                if (_currentParagraphIndex == paragraphIndex)
                {
                    paragraphElement = _currentparagraphElement;
                }
                else
                {
                    if (paragraphIndex >= 0)
                    {
                        paragraphElement = new StructElement("P");
                        ParentElement.Children.Add(paragraphElement);
                        _currentparagraphElement = paragraphElement;
                        _currentParagraphIndex = paragraphIndex;
                    }
                    else
                    {
                        paragraphElement = null;
                        _currentparagraphElement = paragraphElement;
                        _currentParagraphIndex = paragraphIndex;
                    }
                }

                //
                if (paragraphElement != null)
                {
                    graphics.BeginMarkedContent(new TagMcid("P", _tagIndex));
                    var mcil = new McrContentItemLink();
                    mcil.MCID = _tagIndex;
                    mcil.Page = Page;
                    paragraphElement.ContentItems.Add(mcil);
                    _tagIndex++;
                }
            }

            public void TextTagEnd(GcPdfGraphics graphics, TextLayout textLayout, object tag)
            {
                if (_currentparagraphElement != null)
                    graphics.EndMarkedContent();
            }

            public void AddTextArea(RectangleF bounds)
            {
            }
        }
    }
}