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

namespace DsPdfWeb.Demos.Basics
{
    // このサンプルは、タグ付き(構造化)PDF を作成し、TextLayout 内の
    // 個々の段落にタグを付加する方法を示しています。これらの段落を
    // まとめて描画し、ページ間で分割します。
    // この文書を生成するコードは、PaginatedText で使用されている
    // コードに似ていますが、タグが追加されています。
    // タグを表示/閲覧するには、Adobe Acrobat Pro でドキュメントを開き、
    // [表示]-[ナビゲーションパネル]-[タグ]に移動します。
    public class TagTextLayout
    {
        public int CreatePDF(Stream stream)
        {
            var doc = new GcPdfDocument();

            // Part 要素を作成します。これには P(段落)要素が含まれます。
            var sePart = new StructElement("Part");
            doc.StructTreeRoot.Children.Add(sePart);

            // 段落を描画するための TextLayout を作成して設定します。
            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;
            // テキストを追加します(単一のページに収まらないように 20 段落)
            // TextLayout は段落区切り文字として "\r\n" を解釈することに注意してください)。
            // 
            // テキストを取得します(20 段落)。
            var text = Common.Util.getString_ja(1, 0, 20);
            // 個々の段落にタグを付けるには、テキストを段落に分割し、各段落書式の Tag プロパティを
            // 使用して段落のインデックスを段落に追加する必要があります
            // (これは PDF タグに関係なく、TextFormat に関連付けることのできる任意のデータです)。
            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);
            }

            // テキストをレイアウトします。
            tl.PerformLayout(true);
            // 分割オプションを使用して、widow/orphan の制御を提供します。
            var to = new TextSplitOptions(tl)
            {
                MinLinesInFirstParagraph = 2,
                MinLinesInLastParagraph = 2,
            };
            // TextLayoutHandler は ITextLayoutHandler を実装しています。
            // これにより、テキストが描画されるときにそのタグを付けることができます。
            var tlh = new TextLayoutHandler() { ParentElement = sePart };

            // ループ内で、テキストを分割して描画します。
            while (true)
            {
                // 'rest' は、収まりきらなかったテキストを受け入れます。
                var splitResult = tl.Split(to, out TextLayout rest);
                var page = doc.Pages.Add();
                var g = page.Graphics;
                // どのページが表示されているかを TextLayoutHandler に伝えます。
                tlh.Page = page;
                // …そして、それをグラフィックスに関連付けます。
                g.TextLayoutHandler = tlh;
                // 現在のページに収まるテキストを描画し、終了していなければ次のページに進みます。
                g.DrawTextLayout(tl, PointF.Empty);
                if (splitResult != SplitResult.Split)
                    break;
                tl = rest;
            }
            // ドキュメントをタグ付きとしてマークします。
            doc.MarkInfo.Marked = true;

            // PDF ドキュメントを保存します。
            doc.Save(stream);
            return doc.Pages.Count;
        }

        // 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)
            {
            }
        }
    }
}