ReadTagsTableData.vb
''
'' This code is part of Document Solutions for PDF .NET demos.
'' Copyright (c) MESCIUS inc. All rights reserved.
''
Imports System.IO
Imports System.Drawing
Imports System.Linq
Imports System.Collections.Generic
Imports GrapeCity.Documents.Pdf
Imports GrapeCity.Documents.Text
Imports GrapeCity.Documents.Pdf.TextMap
Imports GrapeCity.Documents.Pdf.Structure
Imports GrapeCity.Documents.Pdf.Recognition.Structure
Imports GCTEXT = GrapeCity.Documents.Text
Imports GCDRAW = GrapeCity.Documents.Drawing

'' Find tables and read their data using structure tags.
Public Class ReadTagsTableData
    Private _tf As TextFormat
    Private _tfHdr As TextFormat
    Private _tfPgHdr As TextFormat
    Private _margin As Single = 72

    Public Function CreatePDF(ByVal stream As Stream) As Integer
        '' Set up some text formats:
        _tf = New TextFormat() With {
            .Font = GCTEXT.Font.FromFile(Path.Combine("Resources", "Fonts", "NotoSans-Regular.ttf")),
            .FontSize = 9,
            .ForeColor = Color.Black
        }
        _tfHdr = New TextFormat(_tf) With {
            .Font = GCTEXT.Font.FromFile(Path.Combine("Resources", "Fonts", "NotoSans-Bold.ttf")),
            .FontSize = 11,
            .ForeColor = Color.DarkBlue
        }
        _tfPgHdr = New TextFormat(_tf) With {
            .FontSize = 11,
            .ForeColor = Color.Gray
        }

        '' The resulting PDF:
        Dim doc = New GcPdfDocument()
        Using s = File.OpenRead(Path.Combine("Resources", "PDFs", "C1Olap-QuickStart.pdf"))
            Dim source = New GcPdfDocument()
            source.Load(s)
            PrintAllTables(doc, source)
        End Using
        ' Save the PDF:
        doc.Save(stream)
        '' Done:
        Return doc.Pages.Count
    End Function

    Private Sub PrintAllTables(doc As GcPdfDocument, source As GcPdfDocument)
        '' Get the LogicalStructure and top parent element:
        Dim ls As LogicalStructure = source.GetLogicalStructure()
        If ls Is Nothing OrElse ls.Elements Is Nothing OrElse ls.Elements.Count = 0 Then
            ' No structure tags found:
            Util.AddNote("No structure tags were found in the source document.", doc.Pages.Add())
            Return
        End If
        ' The root element:
        Dim root As Element = ls.Elements(0)

        '' Find and print all tables:
        Dim tables = New List(Of (TextLayout, Page))()
        root.Children.ToList().FindAll(Function(e_) e_.StructElement.Type = "Table").ForEach(Sub(t_) tables.Add(PrintTable(t_)))
        ' Group tables by the page they were found on:
        Dim tablesByPage = tables.GroupBy(Function(t_) t_.Item2.Index)
        ' For each page, print all tables found on that page,
        ' followed by the original page for reference:
        For Each tbp In tablesByPage
            ' The page that will contain the extracted table data:
            Dim pgTables = doc.NewPage()
            ' The page that will contain the source page for reference:
            Dim pgSrc = doc.NewPage()
            ' Print the original page:
            tbp.First().Item2.Draw(pgSrc.Graphics, pgSrc.Bounds)
            ' Add a page header:
            pgSrc.Graphics.DrawString($"Page {tbp.First().Item2.Index + 1} of the source PDF",
                _tfPgHdr, New RectangleF(0, 0, pgSrc.Size.Width, _margin), TextAlignment.Center, ParagraphAlignment.Center, False)
            Dim maxHeight As Single = pgTables.Size.Height - _margin * 2
            Dim y As Single = _margin
            ' Print all table data. For simplicity sake we assume that all table data will fit on a single page:
            For Each 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
            Next
        Next
    End Sub

    Private Function PrintTable(e As Element) As (TextLayout, Page)
        If e.Type <> "Table" Then
            Throw New Exception($"Unexpected: element type must be 'Table' but it is '{e.Type}'.")
        End If

        Dim table = New List(Of List(Of IList(Of ITextParagraph)))()
        Dim maxCols = 0
        ' Select all child elements with type TR - table rows:
        Dim selectRows As Action(Of IReadOnlyList(Of Element)) = Nothing
        selectRows = Sub(elements)
            For Each ec As Element In elements
                If ec.HasChildren Then
                    If ec.StructElement.Type = "TR" Then
                        Dim cells = ec.Children.ToList().FindAll(Function(e_) e_.StructElement.Type = "TD").ToArray()
                        maxCols = Math.Max(maxCols, cells.Length)
                        Dim tableCells = New List(Of IList(Of ITextParagraph))()
                        For Each cell In cells
                            tableCells.Add(cell.GetParagraphs())
                        Next
                        table.Add(tableCells)
                    Else
                        selectRows(ec.Children)
                    End If
                End If
            Next
        End Sub
        selectRows(e.Children)

        '' Show table:
        Dim sourcePage = FindPage(e.StructElement)
        If sourcePage Is Nothing Then
            Throw New Exception("Unexpected: could not find the default page for the table.")
        End If

        Dim tl = New TextLayout(72)

        ' Add table data to the text layout:
        tl.Append($"{vbLf}Table on page {sourcePage.Index + 1} of the source document has {maxCols} column(s) and {table.Count} row(s)." & vbLf & "Data by row:", _tfHdr)
        tl.AppendParagraphBreak()
        Dim irow = 0
        For Each row In table
            Dim icol = 0
            For Each cell In row
                For Each para In cell
                    tl.Append(para.GetText())
                Next
                If row.IndexOf(cell) <= row.Count Then
                    tl.Append(vbTab)
                Else
                    tl.AppendLine()
                End If
                icol += 1
            Next
            irow += 1
            tl.AppendLine()
        Next
        Return (tl, sourcePage)
    End Function

    Private Function FindPage(se As StructElement) As Page
        If se.DefaultPage IsNot Nothing Then
            Return se.DefaultPage
        End If
        If se.HasChildren Then
            For Each child In se.Children
                Dim p = FindPage(child)
                If p IsNot Nothing Then
                    Return p
                End If
            Next
        End If
        Return Nothing
    End Function
End Class