TagTextLayout.vb
''
'' This code is part of GrapeCity Documents for PDF samples.
'' Copyright (c) GrapeCity, Inc. All rights reserved.
''
Imports System.IO
Imports System.Drawing
Imports GrapeCity.Documents.Pdf
Imports GrapeCity.Documents.Text
Imports GrapeCity.Documents.Drawing
Imports GrapeCity.Documents.Pdf.Structure
Imports GrapeCity.Documents.Pdf.MarkedContent

'' 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
    Function CreatePDF(ByVal stream As Stream) As Integer
        Dim doc = New GcPdfDocument()

        '' Create a Part element, it will contain P (paragraph) elements:
        Dim sePart = New StructElement("Part")
        doc.StructTreeRoot.Children.Add(sePart)

        '' Create and set up a TextLayout to render paragraphs:
        Dim 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 vbCrLf as paragraph delimiter):
        ''
        '' Get the text (20 paragraphs):
        Dim text = 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:
        Dim pars = text.Split(New Char() {vbCr, vbLf}, StringSplitOptions.RemoveEmptyEntries)
        For i = 0 To pars.Length - 1
            Dim tf = New TextFormat(tl.DefaultFormat) With {.Tag = i}
            tl.AppendLine(pars(i), tf)
        Next

        '' Layout the text:
        tl.PerformLayout(True)
        '' Use split options to provide widow/orphan control:
        Dim tso = New TextSplitOptions(tl) With {
            .MinLinesInFirstParagraph = 2,
            .MinLinesInLastParagraph = 2
        }
        '' TextLayoutHandler implements ITextLayoutHandler, which
        '' allows to tag the text as it is rendered:
        Dim tlh = New TextLayoutHandler() With {.ParentElement = sePart}

        '' In a loop, split and render the text:
        While True
            '' 'rest' will accept the text that did not fit:
            Dim rest As TextLayout = Nothing
            Dim splitResult = tl.Split(tso, rest)
            Dim page = doc.Pages.Add()
            Dim 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 Then
                Exit While
            End If
            tl = rest
        End While
        '' Mark document as tagged:
        doc.MarkInfo.Marked = True
        ''
        '' Done:
        doc.Save(stream)
        Return doc.Pages.Count
    End Function

    '' Custom class that allows to tag content as it is rendered by TextLayout:
    Private Class TextLayoutHandler : Implements ITextLayoutHandler
        Private _tagIndex As Integer
        Private _currentParagraphIndex As Integer = -1
        Private _currentparagraphElement As StructElement
        Public Property ParentElement As StructElement
        Public Property Page As Page

        Public Sub TextTagBegin(ByVal graphics As GcPdfGraphics, ByVal textLayout As TextLayout, ByVal tag As Object) Implements ITextLayoutHandler.TextTagBegin
            Dim paragraphIndex As Integer
            If TypeOf tag Is Integer Then
                paragraphIndex = CInt(tag)
            Else
                paragraphIndex = -1
            End If

            Dim paragraphElement As StructElement
            If _currentParagraphIndex = paragraphIndex Then
                paragraphElement = _currentparagraphElement
            Else
                paragraphElement = New StructElement("P")
                ParentElement.Children.Add(paragraphElement)
                _currentparagraphElement = paragraphElement
                _currentParagraphIndex = paragraphIndex
            End If
            ''
            graphics.BeginMarkedContent(New TagMcid("P", _tagIndex))
            Dim mcil = New McrContentItemLink()
            mcil.MCID = _tagIndex
            mcil.Page = Page
            paragraphElement.ContentItems.Add(mcil)
            _tagIndex += 1
        End Sub

        Public Sub TextTagEnd(ByVal graphics As GcPdfGraphics, ByVal textLayout As TextLayout, ByVal tag As Object) Implements ITextLayoutHandler.TextTagEnd
            graphics.EndMarkedContent()
        End Sub

        Public Sub AddTextArea(ByVal bounds As RectangleF) Implements ITextLayoutHandler.AddTextArea
        End Sub
    End Class
End Class