DynamicTable.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 System.Text
Imports GrapeCity.Documents.Pdf
Imports GrapeCity.Documents.Text
Imports GrapeCity.Documents.Html

'' This sample shows how to insert an HTML table with a varying number of rows
'' that might not fit on a single page, into a PDF document starting at an arbitrary
'' position on the page (all data rows must have the same height though).
'' We first create a table with a single data row, measure its height,
'' then create a similar table but with two data rows and measure it too.
'' This allows us to find out the header and data rows' heights, and render
'' the table to a PDF starting at the desired position on the page, and split
'' it into additional pages as needed.
''
'' Please see notes in comments at the top of HelloWorldHtml
'' sample code for details on adding GcHtml to your projects.
Public Class DynamicTable
    Function CreatePDF(ByVal stream As Stream) As Integer
        '' This tag is used to insert the prepared table HTML code
        '' into the HTML page template that defines the CSS styles etc.
        '' (Using this tag allows to use string.Format when building
        '' the table HTML code, as otherwise curly braces in style
        '' definitions would interfere with format specifiers.)
        Const TTAG = "___TABLE___"

        '' The HTML page template:
        Const tableTpl =
                "<!DOCTYPE html>" +
                "<html>" +
                "<head>" +
                "<style>" +
                "#employees {" +
                "  font-family: 'Trebuchet MS', Arial, Helvetica, sans-serif;" +
                "  border-collapse: collapse;" +
                "  width: 100%;" +
                "}" +
                "" +
                "#employees td, #employees th {" +
                "  border: 1px solid #ddd;" +
                "  padding: 8px;" +
                "}" +
                "" +
                "#employees tr:nth-child(even){background-color: #f2f2f2;}" +
                "" +
                "#employees tr:hover {background-color: #ddd;}" +
                "" +
                "#employees th {" +
                "  padding-top: 12px;" +
                "  padding-bottom: 12px;" +
                "  text-align: left;" +
                "  background-color: #3377ff;" +
                "  color: white;" +
                "}" +
                "</style>" +
                "</head>" +
                "<body>" +
                "" +
                TTAG +
                "" +
                "</body>" +
                "</html>"

        '' The table HTML code format:
        Const tableFmt =
                "<table id='employees'>" +
                "  <tr>" +
                "    <th>Index</th>" +
                "    <th>Lorem</th>" +
                "    <th>Ipsum</th>" +
                "  </tr>" +
                "{0}" +
                "</table>"

        '' The table row HTML code format:
        Const dataRowFmt =
                "  <tr>" +
                "    <td>{0}</td>" +
                "    <td>{1}</td>" +
                "    <td>{2}</td>" +
                "  </tr>"

        '' Create a new PDF document:
        Dim doc = New GcPdfDocument()
        '' Add a page:
        Dim page = doc.NewPage()
        '' Add a page, get its graphics:
        Dim g = page.Graphics

        '' Set up HTML to PDF formatting options.
        '' The most important are the size limits, in this case
        '' we do not limit the height as we will adjust it programmatically.
        '' Note that in HtmlToPdfFormat, sizes are specified in inches:
        Dim hf = New HtmlToPdfFormat(False) With {.MaxPageWidth = page.Size.Width / 72}

        '' HTML code for a single data row (with sample data):
        Dim dataRow = String.Format(dataRowFmt, "a", "b", "c")
        '' HTML page with a table that has a single data row:
        Dim thtml = tableTpl.Replace(TTAG, String.Format(tableFmt, dataRow))
        '' Measure the HTML for the current GcPdfGraphics:
        Dim s1 = g.MeasureHtml(thtml, hf)
        '' Same HTML page but with two data rows:
        thtml = tableTpl.Replace(TTAG, String.Format(tableFmt, dataRow + dataRow))
        '' Measure the new HTML:
        Dim s2 = g.MeasureHtml(thtml, hf)
        '' Calculate data row and header row heights:
        Dim rowHeight = s2.Height - s1.Height
        Dim headerHeight = s1.Height - rowHeight

        '' Add a note at the top of the first page:
        Dim nrc = Util.AddNote(
            "Here we render an HTML table with an unknown number of rows " +
            "that starts at a specified position on the first page, " +
            "and may span multiple pages.",
            page)

        '' Set up for building the table with random data:
        Dim lorems = Util.LoremWords()
        Dim rnd = Util.NewRandom()
        Dim sb = New StringBuilder()

        '' Page layout parameters:
        Dim marginx = nrc.Left
        Dim marginy = nrc.Top
        Dim x = marginx
        Dim y = nrc.Bottom + 36
        Dim tbottom = nrc.Bottom + 36 + headerHeight
        '' A random number of data rows to render:
        Dim nrows = rnd.Next(100, 200)
        '' Generate and render the table, adding continuation pages as needed:
        For i = 0 To nrows - 1
            sb.AppendFormat(dataRowFmt, i, lorems(rnd.Next(lorems.Count)), lorems(rnd.Next(lorems.Count)))
            tbottom += rowHeight
            Dim lastPage = i = (nrows - 1)
            If tbottom >= page.Size.Height - 72 Or lastPage Then
                Dim html = tableTpl.Replace(TTAG, String.Format(tableFmt, sb.ToString()))
                Dim size As SizeF
                Dim ok = g.DrawHtml(html, x, y, New HtmlToPdfFormat(False) With {.MaxPageWidth = (page.Size.Width - marginx * 2) / 72}, size)
                If Not lastPage Then
                    page = doc.NewPage()
                    g = page.Graphics
                    y = 72
                    tbottom = y + headerHeight
                    sb.Clear()
                End If
            End If
        Next

        '' Done:
        doc.Save(stream)
        Return doc.Pages.Count
    End Function
End Class