TagTextLayout.cs
// 完毕:
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中使用的代码类似,
    // 但添加了标签。
    // 要查看/探索标签,请在 Adob​​e 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.LoremIpsum(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);
            // 使用分割选项来提供寡妇/孤儿控制:
            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;

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