ReadTagsTableData.cs
// 
// このコードは、DioDocs for PDF のサンプルの一部として提供されています。
// © 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.Pdf.TextMap;
using GrapeCity.Documents.Pdf.Structure;
using GrapeCity.Documents.Pdf.Recognition.Structure;
using GCTEXT = GrapeCity.Documents.Text;
using GCDRAW = GrapeCity.Documents.Drawing;

namespace DsPdfWeb.Demos
{
    // 構造タグを使用してテーブルを検索し、そのデータを読み取ります。
    public class ReadTagsTableData
    {
        private TextFormat _tf, _tfHdr, _tfPgHdr;
        private float _margin = 72;

        public int CreatePDF(Stream stream)
        {
            // テキストフォーマットを設定します。
            _tf = new TextFormat()
            {
                Font = GCTEXT.Font.FromFile(Path.Combine("Resources", "Fonts", "ipag.ttc")),
                FontSize = 9,
                ForeColor = Color.Black
            };
            _tfHdr = new TextFormat(_tf)
            {
                FontBold = true,
                FontSize = 11,
                ForeColor = Color.DarkBlue
            };
            _tfPgHdr = new TextFormat(_tf)
            {
                FontSize = 11,
                ForeColor = Color.Gray
            };

            // 結果のPDFを生成します。
            var doc = new GcPdfDocument();
            using (var s = File.OpenRead(Path.Combine("Resources", "PDFs", "C1Olap-QuickStart.pdf")))
            {
                var source = new GcPdfDocument();
                source.Load(s);
                PrintAllTables(doc, source);
            }
            // PDFを保存します。
            doc.Save(stream);
            return doc.Pages.Count;
        }

        private void PrintAllTables(GcPdfDocument doc, GcPdfDocument source)
        {
            // LogicalStructureと最上位の親要素を取得します。
            LogicalStructure ls = source.GetLogicalStructure();
            if (ls == null || ls.Elements == null || ls.Elements.Count == 0)
            {
                // 構造タグが見つからない場合:
                Common.Util.AddNote("ドキュメントに構造タグが見つかりませんでした。", doc.Pages.Add());
                return;
            }
            // ルート要素:
            Element root = ls.Elements[0];

            // すべてのテーブルを検索して出力します。
            var tables = new List<(TextLayout, Page)>();
            root.Children.ToList().FindAll(e_ => e_.StructElement.Type == "Table").ForEach(t_ => tables.Add(PrintTable(t_)));
            // 見つかったページごとにテーブルをまとめます。
            var tablesByPage = tables.GroupBy(t_ => t_.Item2.Index);
            // 各ページごとに、そのページにあるすべてのテーブルを出力し、
            // その後に参照用として元のページを出力します。
            foreach (var tbp in tablesByPage)
            {
                // 抽出されたテーブルデータを出力するページです。
                var pgTables = doc.NewPage();
                // 参照用の元ページを出力するページです。
                var pgSrc = doc.NewPage();
                // 元のページを出力します。
                tbp.First().Item2.Draw(pgSrc.Graphics, pgSrc.Bounds);
                // ページヘッダーを追加します。
                pgSrc.Graphics.DrawString($"元のPDFの {tbp.First().Item2.Index + 1} ページ目",
                    _tfPgHdr, new RectangleF(0, 0, pgSrc.Size.Width, _margin), TextAlignment.Center, ParagraphAlignment.Center, false);
                //
                float maxHeight = pgTables.Size.Height - _margin * 2;
                float y = _margin;
                // すべてのテーブルデータを出力します。
                // 簡略化のため、すべてのテーブルデータが1ページに収まることを想定しています。
                foreach (var t in tbp)
                {
                    t.Item1.MaxHeight = maxHeight;
                    t.Item1.MaxWidth = pgTables.Size.Width - _margin * 2;
                    pgTables.Graphics.DrawTextLayout(t.Item1, new PointF(_margin, y));
                    maxHeight -= t.Item1.ContentHeight + _margin;
                    y += t.Item1.ContentHeight + _margin;
                }
            }
        }

        private (TextLayout, Page) PrintTable(Element e)
        {
            if (e.Type != "Table")
                throw new Exception($"予期しないエラー:要素タイプは'Table'でなければなりませんが、'{e.Type}'になっています。");

            List<List<IList<ITextParagraph>>> table = new List<List<IList<ITextParagraph>>>();
            int maxCols = 0;
            // タイプがTR(テーブル行)であるすべての子要素を選択します。
            void SelectRows(IReadOnlyList<Element> elements)
            {
                foreach (Element ec in elements)
                {
                    if (ec.HasChildren)
                    {
                        if (ec.StructElement.Type == "TR")
                        {
                            var cells = ec.Children.ToList().FindAll((e_) => e_.StructElement.Type == "TD").ToArray();
                            maxCols = Math.Max(maxCols, cells.Length);
                            List<IList<ITextParagraph>> tableCells = new List<IList<ITextParagraph>>();
                            foreach (var cell in cells)
                                tableCells.Add(cell.GetParagraphs());
                            table.Add(tableCells);
                        }
                        else
                            SelectRows(ec.Children);
                    }
                }
            }
            SelectRows(e.Children);

            // テーブルのページを取得します。
            var sourcePage = FindPage(e.StructElement);
            if (sourcePage == null)
                throw new Exception("予期しないエラー:テーブルのデフォルトページが見つかりませんでした。");

            var tl = new TextLayout(72);

            // テキストレイアウトにテーブルデータを追加します。
            tl.Append($"\n元のドキュメントの {sourcePage.Index + 1} ページ目のテーブルには、列が {maxCols} 列と、行が {table.Count} 行あります。\nデータは以下のとおりです:", _tfHdr);
            tl.AppendParagraphBreak();
            int irow = 0;
            foreach (var row in table)
            {
                int icol = 0;
                foreach (var cell in row)
                {
                    foreach (var para in cell)
                    {
                        tl.Append(para.GetText());
                    }
                    if (row.IndexOf(cell) <= row.Count)
                        tl.Append("\t");
                    else
                        tl.AppendLine();
                    ++icol;
                }
                ++irow;
                tl.AppendLine();
            }
            return (tl, sourcePage);
        }

        private Page FindPage(StructElement se)
        {
            if (se.DefaultPage != null)
                return se.DefaultPage;
            if (se.HasChildren)
                foreach (var child in se.Children)
                {
                    var p = FindPage(child);
                    if (p != null)
                        return p;
                }
            return null;
        }
    }
}