Creating PDF Tables Using Pure Go
While working with PDFs, you may have come across the challenge of creating a table and placing it at the right spot on the page. Like many others, you must have spent hours fixating on the borders, the shape and the position of the table and would have kept trying to fix it.
Anyone who has ever had to work with PDF reports can relate to that, which is why UniPDF spent a considerable amount of effort on perfecting the creator package. It allows you to create tables, edit them and place them elegantly on pages, all through the power of golang.
UniPDF is powered by code written in pure go, which makes it robust, easy to deploy and free from bugs that can crash your application during runtime. Since it’s written in go, it has a steep learning curve and you can get started with creating interesting documents, in a matter of minutes.
Recently, we’ve discussed how you can use the UniDoc library to create letters, work with spreadsheets, digitally sign your PDF documents and do a whole lot more. Now lets see how you can use the creator package to create tables in your PDF documents.
UniDoc hosts a playground, where you can test out the power of UniPDF and UniOffice without having to configure the library on your system. This allows you to test out concepts, create code super fast and share your creations, without having to deal with any of the backend. Learn more about UniDoc playground through this introductory tutorial.
Create a Simple Table
All examples shown in this article are built using the UniDoc playground. You can easily open each individual example, play around with it, save it and share with your colleagues.
The following playground example highlights how you can create a simple table using UniPDF library. The example is inspired from this GitHub example, you can check it out here and test out other examples in the repository as well.
/*
* This example showcases PDF tables features using unipdf's creator package.
* The output is saved as unipdf-simple-tables.pdf which illustrates how to
* create a basic table.
*/
package main
import (
"fmt"
"log"
"github.com/unidoc/unipdf/v3/creator"
"github.com/unidoc/unipdf/v3/model"
)
func main() {
c := creator.New()
c.SetPageMargins(50, 50, 50, 50)
// Create report fonts.
// UniPDF supports a number of font-families, which can be accessed using model.
font, err := model.NewStandard14Font(model.HelveticaName)
if err != nil {
log.Fatal(err)
}
fontBold, err := model.NewStandard14Font(model.HelveticaBoldName)
if err != nil {
log.Fatal(err)
}
// Generate basic usage chapter.
if err := basicUsage(c, font, fontBold); err != nil {
log.Fatal(err)
}
// Write to output file.
if err := c.WriteToFile("unipdf-simple-tables.pdf"); err != nil {
log.Fatal(err)
}
}
func basicUsage(c *creator.Creator, font, fontBold *model.PdfFont) error {
// Create chapter.
ch := c.NewChapter("Basic usage")
ch.SetMargins(0, 0, 50, 0)
ch.GetHeading().SetFont(font)
ch.GetHeading().SetFontSize(18)
ch.GetHeading().SetColor(creator.ColorRGBFrom8bit(72, 86, 95))
// Draw subchapters.
contentAlignH(c, ch, font, fontBold)
contentAlignV(c, ch, font, fontBold)
contentWrapping(c, ch, font, fontBold)
contentOverflow(c, ch, font, fontBold)
// Draw chapter.
if err := c.Draw(ch); err != nil {
return err
}
return nil
}
func contentAlignH(c *creator.Creator, ch *creator.Chapter, font, fontBold *model.PdfFont) {
// Create subchapter.
sc := ch.NewSubchapter("Content horizontal alignment")
sc.SetMargins(0, 0, 30, 0)
sc.GetHeading().SetFont(font)
sc.GetHeading().SetFontSize(13)
sc.GetHeading().SetColor(creator.ColorRGBFrom8bit(72, 86, 95))
// Create subchapter description.
desc := c.NewStyledParagraph()
desc.SetMargins(0, 0, 10, 0)
desc.Append("Cell content can be aligned horizontally left, right or it can be centered.")
sc.Add(desc)
// Create table.
table := c.NewTable(3)
table.SetMargins(0, 0, 10, 0)
drawCell := func(text string, font *model.PdfFont, align creator.CellHorizontalAlignment) {
p := c.NewStyledParagraph()
p.Append(text).Style.Font = font
cell := table.NewCell()
cell.SetBorder(creator.CellBorderSideAll, creator.CellBorderStyleSingle, 1)
cell.SetHorizontalAlignment(align)
cell.SetContent(p)
}
// Draw table header.
drawCell("Align left", fontBold, creator.CellHorizontalAlignmentLeft)
drawCell("Align center", fontBold, creator.CellHorizontalAlignmentCenter)
drawCell("Align right", fontBold, creator.CellHorizontalAlignmentRight)
// Draw table content.
for i := 0; i < 5; i++ {
num := i + 1
drawCell(fmt.Sprintf("Product #%d", num), font, creator.CellHorizontalAlignmentLeft)
drawCell(fmt.Sprintf("Description #%d", num), font, creator.CellHorizontalAlignmentCenter)
drawCell(fmt.Sprintf("$%d", num*10), font, creator.CellHorizontalAlignmentRight)
}
sc.Add(table)
}
func contentAlignV(c *creator.Creator, ch *creator.Chapter, font, fontBold *model.PdfFont) {
// Create subchapter.
sc := ch.NewSubchapter("Content vertical alignment")
sc.SetMargins(0, 0, 30, 0)
sc.GetHeading().SetFont(font)
sc.GetHeading().SetFontSize(13)
sc.GetHeading().SetColor(creator.ColorRGBFrom8bit(72, 86, 95))
// Create subchapter description.
desc := c.NewStyledParagraph()
desc.SetMargins(0, 0, 10, 0)
desc.Append("Cell content can be positioned vertically at the top, bottom or in the middle of the cell.")
sc.Add(desc)
// Create table.
table := c.NewTable(3)
table.SetMargins(0, 0, 10, 0)
drawCell := func(text string, font *model.PdfFont, fontSize float64, align creator.CellVerticalAlignment) {
p := c.NewStyledParagraph()
chunk := p.Append(text)
chunk.Style.Font = font
chunk.Style.FontSize = fontSize
cell := table.NewCell()
cell.SetBorder(creator.CellBorderSideAll, creator.CellBorderStyleSingle, 1)
cell.SetVerticalAlignment(align)
cell.SetHorizontalAlignment(creator.CellHorizontalAlignmentCenter)
cell.SetContent(p)
}
// Draw table header.
drawCell("Align top", fontBold, 10, creator.CellVerticalAlignmentMiddle)
drawCell("Align bottom", fontBold, 10, creator.CellVerticalAlignmentMiddle)
drawCell("Align middle", fontBold, 10, creator.CellVerticalAlignmentMiddle)
// Draw table content.
for i := 0; i < 5; i++ {
num := i + 1
fontSize := float64(num) * 2
drawCell(fmt.Sprintf("Product #%d", num), font, fontSize, creator.CellVerticalAlignmentTop)
drawCell(fmt.Sprintf("$%d", num*10), font, fontSize, creator.CellVerticalAlignmentBottom)
drawCell(fmt.Sprintf("Description #%d", num), font, fontSize, creator.CellVerticalAlignmentMiddle)
}
sc.Add(table)
}
func contentWrapping(c *creator.Creator, ch *creator.Chapter, font, fontBold *model.PdfFont) {
// Create subchapter.
sc := ch.NewSubchapter("Content wrapping")
sc.SetMargins(0, 0, 30, 0)
sc.GetHeading().SetFont(font)
sc.GetHeading().SetFontSize(13)
sc.GetHeading().SetColor(creator.ColorRGBFrom8bit(72, 86, 95))
// Create subchapter description.
desc := c.NewStyledParagraph()
desc.SetMargins(0, 0, 10, 0)
desc.Append("Cell text content is automatically broken into lines, depeding on the cell size.")
sc.Add(desc)
// Create table.
table := c.NewTable(4)
table.SetColumnWidths(0.25, 0.2, 0.25, 0.3)
table.SetMargins(0, 0, 10, 0)
drawCell := func(text string, font *model.PdfFont, align creator.TextAlignment) {
p := c.NewStyledParagraph()
p.SetTextAlignment(align)
p.SetMargins(2, 2, 0, 0)
p.Append(text).Style.Font = font
cell := table.NewCell()
cell.SetBorder(creator.CellBorderSideAll, creator.CellBorderStyleSingle, 1)
cell.SetContent(p)
cell.SetIndent(0)
}
// Draw table header.
drawCell("Align left", fontBold, creator.TextAlignmentLeft)
drawCell("Align center", fontBold, creator.TextAlignmentCenter)
drawCell("Align right", fontBold, creator.TextAlignmentRight)
drawCell("Align justify", fontBold, creator.TextAlignmentCenter)
// Draw table content.
content := "Maecenas tempor nibh gravida nunc laoreet, ut rhoncus justo ultricies. Mauris nec purus sit amet purus tincidunt efficitur tincidunt non dolor. Aenean nisl eros, volutpat vitae dictum id, facilisis ac felis. Integer lacinia, turpis at fringilla posuere, erat tortor ultrices orci, non tempor neque mauris ac neque. Morbi blandit ante et lacus ornare, ut vulputate massa dictum."
drawCell(content, font, creator.TextAlignmentLeft)
drawCell(content, font, creator.TextAlignmentCenter)
drawCell(content, font, creator.TextAlignmentRight)
drawCell(content, font, creator.TextAlignmentJustify)
sc.Add(table)
}
func contentOverflow(c *creator.Creator, ch *creator.Chapter, font, fontBold *model.PdfFont) {
// Create subchapter.
sc := ch.NewSubchapter("Content overflow")
sc.SetMargins(0, 0, 30, 0)
sc.GetHeading().SetFont(font)
sc.GetHeading().SetFontSize(13)
sc.GetHeading().SetColor(creator.ColorRGBFrom8bit(72, 86, 95))
// Create subchapter description.
desc := c.NewStyledParagraph()
desc.SetMargins(0, 0, 10, 0)
desc.Append("Cell text content which not fit in the available space could be truncated to fit.")
sc.Add(desc)
// Create table.
table := c.NewTable(2)
table.SetColumnWidths(0.3, 0.7)
table.SetMargins(0, 0, 10, 0)
drawCell := func(text string, font *model.PdfFont, enableWrap bool, overflow creator.TextOverflow) {
p := c.NewStyledParagraph()
p.SetEnableWrap(enableWrap)
p.SetTextOverflow(overflow)
p.SetMargins(2, 2, 0, 0)
p.Append(text).Style.Font = font
cell := table.NewCell()
cell.SetBorder(creator.CellBorderSideAll, creator.CellBorderStyleSingle, 1)
cell.SetContent(p)
cell.SetIndent(0)
}
content := "Maecenas tempor nibh gravida nunc laoreet, ut rhoncus justo ultricies. Mauris nec purus sit amet purus tincidunt efficitur tincidunt non dolor. Aenean nisl eros, volutpat vitae dictum id, facilisis ac felis. Integer lacinia, turpis at fringilla posuere, erat tortor ultrices orci, non tempor neque mauris ac neque. Morbi blandit ante et lacus ornare, ut vulputate massa dictum."
// Draw table header.
drawCell("Overflow Visible With Wrapping", fontBold, true, creator.TextOverflowVisible)
drawCell(content, font, true, creator.TextOverflowVisible)
drawCell("Overflow Visible Without Wrapping", fontBold, true, creator.TextOverflowVisible)
drawCell(content, font, false, creator.TextOverflowVisible)
drawCell("Overflow Hidden", fontBold, true, creator.TextOverflowHidden)
drawCell(content, font, false, creator.TextOverflowHidden)
sc.Add(table)
}
The flow of the functions in the above example is simply:
main() -> basicUsage() -> contentAlignH()
The main()
function calls the basicUsage()
function, which calls the contentAlignH
function. In the basicUsage(), you are simply creating a new chapter in your PDF report and the contentAlignH() function creates a subchapter.
Partitioning the program into separate functions like above helps in extending the program when needed. Each new subchapter can be created in a separate function that can be called from the basicUsage() method.
Creating a table is as simple as table := c.NewTable(3)
, where the parameter denotes the number of columns. You can see this function call at line number 76 in the embedded playground example above.
The magic happens in the inline function that is created at line 79, which is the drawCell()
function. The function accepts the text, font family and the alignment of the text as parameters and creates a new styled paragraph which is inserted into each corresponding cell.
drawCell()
is called afterwards to create the table header and programmatically create a list of five products by calling a loop.
Style Your Table
UniPDF is focused on making your life easier while you’re working on creating tables in your PDF reports. This is why you can style your table in a number of different ways, according to your requirements.
The following playground example highlights how you can change the type of borders and update the background of cells.
/*
* This example showcases PDF tables features using unipdf's creator package.
* The output is saved as unipdf-style-tables.pdf which illustrates how to
* configure style in table.
*/
package main
import (
"log"
"os"
"github.com/unidoc/unipdf/v3/common/license"
"github.com/unidoc/unipdf/v3/contentstream/draw"
"github.com/unidoc/unipdf/v3/creator"
"github.com/unidoc/unipdf/v3/model"
)
func main() {
c := creator.New()
c.SetPageMargins(50, 50, 50, 50)
// Create report fonts.
// UniPDF supports a number of font-families, which can be accessed using model.
font, err := model.NewStandard14Font(model.HelveticaName)
if err != nil {
log.Fatal(err)
}
fontBold, err := model.NewStandard14Font(model.HelveticaBoldName)
if err != nil {
log.Fatal(err)
}
// Generate styling content chapter.
if err := stylingContent(c, font, fontBold); err != nil {
log.Fatal(err)
}
// Write to output file.
if err := c.WriteToFile("unipdf-style-tables.pdf"); err != nil {
log.Fatal(err)
}
}
func stylingContent(c *creator.Creator, font, fontBold *model.PdfFont) error {
c.NewPage()
// Create chapter.
ch := c.NewChapter("Styling content")
ch.SetMargins(0, 0, 50, 0)
ch.GetHeading().SetFont(font)
ch.GetHeading().SetFontSize(18)
ch.GetHeading().SetColor(creator.ColorRGBFrom8bit(72, 86, 95))
// Draw subchapters.
contentBorders(c, ch, font, fontBold)
contentBackground(c, ch, font, fontBold)
// Draw chapter.
if err := c.Draw(ch); err != nil {
return err
}
return nil
}
func contentBorders(c *creator.Creator, ch *creator.Chapter, font, fontBold *model.PdfFont) {
// Create subchapter.
sc := ch.NewSubchapter("Cell borders")
sc.SetMargins(0, 0, 30, 0)
sc.GetHeading().SetFont(font)
sc.GetHeading().SetFontSize(13)
sc.GetHeading().SetColor(creator.ColorRGBFrom8bit(72, 86, 95))
// Create subchapter description.
desc := c.NewStyledParagraph()
desc.SetMargins(0, 0, 10, 0)
desc.Append("Customizable cell border properties:\n\n")
desc.Append("\u2022 Border side: left, right, top, bottom, all\n")
desc.Append("\u2022 Border style: single or double\n")
desc.Append("\u2022 Border line style: solid or dashed\n")
desc.Append("\u2022 Border color\n")
desc.Append("\u2022 Border width\n")
sc.Add(desc)
// Create table.
table := c.NewTable(2)
table.SetMargins(0, 0, 10, 0)
drawCell := func(text string, font *model.PdfFont, borderStyle creator.CellBorderStyle, borderSide creator.CellBorderSide, borderWidth float64, borderColor creator.Color, lineStyle draw.LineStyle) {
p := c.NewStyledParagraph()
chunk := p.Append(text)
chunk.Style.Font = font
cell := table.NewCell()
cell.SetBorder(borderSide, borderStyle, borderWidth)
cell.SetBorderColor(borderColor)
cell.SetBorderLineStyle(lineStyle)
cell.SetHorizontalAlignment(creator.CellHorizontalAlignmentCenter)
cell.SetContent(p)
}
// Draw table header.
drawCell("Border right single width 2", fontBold, creator.CellBorderStyleSingle, creator.CellBorderSideRight, 2, creator.ColorBlack, draw.LineStyleSolid)
drawCell("Border right double", fontBold, creator.CellBorderStyleDouble, creator.CellBorderSideRight, 1, creator.ColorBlack, draw.LineStyleSolid)
// Draw table content.
drawCell("Border top single width 2", fontBold, creator.CellBorderStyleSingle, creator.CellBorderSideTop, 2, creator.ColorBlack, draw.LineStyleSolid)
drawCell("Border bottom single width 2", fontBold, creator.CellBorderStyleSingle, creator.CellBorderSideBottom, 2, creator.ColorBlack, draw.LineStyleSolid)
drawCell("Border all double", fontBold, creator.CellBorderStyleDouble, creator.CellBorderSideAll, 1, creator.ColorBlack, draw.LineStyleSolid)
drawCell("No border", fontBold, creator.CellBorderStyleSingle, creator.CellBorderSideAll, 0, creator.ColorBlack, draw.LineStyleSolid)
drawCell("Border bottom single green", fontBold, creator.CellBorderStyleSingle, creator.CellBorderSideBottom, 1, creator.ColorGreen, draw.LineStyleSolid)
drawCell("Border top double red", fontBold, creator.CellBorderStyleDouble, creator.CellBorderSideTop, 1, creator.ColorRed, draw.LineStyleSolid)
drawCell("Border all single yellow", fontBold, creator.CellBorderStyleSingle, creator.CellBorderSideAll, 1, creator.ColorYellow, draw.LineStyleSolid)
drawCell("Border right double dashed", fontBold, creator.CellBorderStyleDouble, creator.CellBorderSideRight, 1, creator.ColorBlack, draw.LineStyleDashed)
drawCell("Border bottom double solid", fontBold, creator.CellBorderStyleDouble, creator.CellBorderSideBottom, 1, creator.ColorBlack, draw.LineStyleSolid)
drawCell("Border bottom double dashed green", fontBold, creator.CellBorderStyleDouble, creator.CellBorderSideBottom, 1, creator.ColorGreen, draw.LineStyleDashed)
drawCell("Border left double blue", fontBold, creator.CellBorderStyleDouble, creator.CellBorderSideLeft, 1, creator.ColorBlue, draw.LineStyleSolid)
drawCell("Border right double red", fontBold, creator.CellBorderStyleDouble, creator.CellBorderSideRight, 1, creator.ColorRed, draw.LineStyleSolid)
drawCell("Border all double yellow", fontBold, creator.CellBorderStyleDouble, creator.CellBorderSideAll, 1, creator.ColorYellow, draw.LineStyleSolid)
drawCell("Border all double dashed blue", fontBold, creator.CellBorderStyleDouble, creator.CellBorderSideAll, 1, creator.ColorBlue, draw.LineStyleDashed)
sc.Add(table)
}
func contentBackground(c *creator.Creator, ch *creator.Chapter, font, fontBold *model.PdfFont) {
// Create subchapter.
sc := ch.NewSubchapter("Cell background")
sc.SetMargins(0, 0, 30, 0)
sc.GetHeading().SetFont(font)
sc.GetHeading().SetFontSize(13)
sc.GetHeading().SetColor(creator.ColorRGBFrom8bit(72, 86, 95))
// Create subchapter description.
desc := c.NewStyledParagraph()
desc.SetMargins(0, 0, 10, 0)
desc.Append("The background color of the cells is also customizable.")
sc.Add(desc)
// Create table.
table := c.NewTable(4)
table.SetMargins(0, 0, 10, 0)
drawCell := func(text string, font *model.PdfFont, bgColor creator.Color) {
p := c.NewStyledParagraph()
p.SetMargins(2, 2, 0, 0)
chunk := p.Append(text)
chunk.Style.Font = font
chunk.Style.Color = creator.ColorWhite
cell := table.NewCell()
cell.SetBorder(creator.CellBorderSideAll, creator.CellBorderStyleSingle, 1)
cell.SetBackgroundColor(bgColor)
cell.SetContent(p)
cell.SetHorizontalAlignment(creator.CellHorizontalAlignmentCenter)
cell.SetIndent(0)
}
// Draw table content.
for i := 0; i < 15; i++ {
drawCell("Content", fontBold, creator.ColorRGBFrom8bit(byte(i*20), byte(i*7), byte(i*4)))
drawCell("Content", fontBold, creator.ColorRGBFrom8bit(byte(i*10), byte(i*20), byte(i*4)))
drawCell("Content", fontBold, creator.ColorRGBFrom8bit(byte(i*15), byte(i*6), byte(i*9)))
drawCell("Content", fontBold, creator.ColorRGBFrom8bit(byte(i*6), byte(i*7), byte(i*25)))
}
sc.Add(table)
}
Similar to the previous example, we have one main stylingContent() method that creates a chapter. Further, we have two separate methods that deal with changing borders and updating the background design respectively. Each method creates a separate subchapter.
There is a similar drawCell() method that creates a cell with custom configuration, which is repeatedly called to create cells with customized borders.
A similar drawCell() method also exists in the contentBackground() method that updates the background design of the cell.
The output of all the code is saved in a pdf named styled_table.pdf, which you can view in the playground.
Span a Column Across Multiple Columns
A common challenge that people working with tables in PDF documents face is how to create a column that spans multiple columns. The challenge may seem trivial at first, but becomes quite complicated when trying to actually implement the concept.
The following playground example highlights how you can use the UniPDF library to tackle this issue and create columns that span the length of multiple columns.
/*
* This example showcases PDF tables features using unipdf's creator package.
* The output is saved as unipdf-colSpan-tables.pdf which illustrates how to
* configure columns that span multiple columns.
*/
package main
import (
"log"
"os"
"github.com/unidoc/unipdf/v3/common/license"
"github.com/unidoc/unipdf/v3/creator"
"github.com/unidoc/unipdf/v3/model"
)
func main() {
c := creator.New()
c.SetPageMargins(50, 50, 50, 50)
// Create report fonts.
// UniPDF supports a number of font-families, which can be accessed using model.
font, err := model.NewStandard14Font(model.HelveticaName)
if err != nil {
log.Fatal(err)
}
fontBold, err := model.NewStandard14Font(model.HelveticaBoldName)
if err != nil {
log.Fatal(err)
}
// Create Column Span example
if err := columnSpan(c, font, fontBold); err != nil {
log.Fatal(err)
}
// Write to output file.
if err := c.WriteToFile("unipdf-colSpan-tables.pdf"); err != nil {
log.Fatal(err)
}
}
func columnSpan(c *creator.Creator, font, fontBold *model.PdfFont) error {
c.NewPage()
// Create subchapter.
ch := c.NewChapter("Column span")
ch.SetMargins(0, 0, 30, 0)
ch.GetHeading().SetFont(font)
ch.GetHeading().SetFontSize(13)
ch.GetHeading().SetColor(creator.ColorRGBFrom8bit(72, 86, 95))
// Create subchapter description.
desc := c.NewStyledParagraph()
desc.SetMargins(0, 0, 10, 0)
desc.Append("Table content can be configured to span a specified number of cells.")
ch.Add(desc)
// Create table.
table := c.NewTable(5)
table.SetMargins(0, 0, 10, 0)
drawCell := func(text string, font *model.PdfFont, colspan int, color, bgColor creator.Color) {
p := c.NewStyledParagraph()
p.SetMargins(2, 2, 0, 0)
chunk := p.Append(text)
chunk.Style.Font = font
chunk.Style.Color = color
cell := table.MultiColCell(colspan)
cell.SetBorder(creator.CellBorderSideAll, creator.CellBorderStyleSingle, 1)
cell.SetBackgroundColor(bgColor)
cell.SetContent(p)
cell.SetHorizontalAlignment(creator.CellHorizontalAlignmentCenter)
cell.SetIndent(0)
}
// Draw table content.
// Colspan 1 + 1 + 1 + 1 + 1.
drawCell("1", fontBold, 1, creator.ColorWhite, creator.ColorBlue)
drawCell("1", fontBold, 1, creator.ColorWhite, creator.ColorBlue)
drawCell("1", fontBold, 1, creator.ColorWhite, creator.ColorBlue)
drawCell("1", fontBold, 1, creator.ColorWhite, creator.ColorBlue)
drawCell("1", fontBold, 1, creator.ColorWhite, creator.ColorBlue)
// Colspan 2 + 3.
drawCell("2", fontBold, 2, creator.ColorWhite, creator.ColorRed)
drawCell("3", fontBold, 3, creator.ColorWhite, creator.ColorRed)
// Colspan 4 + 1.
drawCell("4", fontBold, 4, creator.ColorBlack, creator.ColorGreen)
drawCell("1", fontBold, 1, creator.ColorBlack, creator.ColorGreen)
// Colspan 2 + 2 + 1.
drawCell("2", fontBold, 2, creator.ColorBlack, creator.ColorYellow)
drawCell("2", fontBold, 2, creator.ColorBlack, creator.ColorYellow)
drawCell("1", fontBold, 1, creator.ColorBlack, creator.ColorYellow)
// Colspan 5.
drawCell("5", fontBold, 5, creator.ColorWhite, creator.ColorBlack)
// Colspan 1 + 2 + 1 + 1.
drawCell("1", fontBold, 1, creator.ColorBlack, creator.ColorYellow)
drawCell("2", fontBold, 2, creator.ColorBlack, creator.ColorYellow)
drawCell("1", fontBold, 1, creator.ColorBlack, creator.ColorYellow)
drawCell("1", fontBold, 1, creator.ColorBlack, creator.ColorYellow)
// Colspan 1 + 4.
drawCell("1", fontBold, 1, creator.ColorBlack, creator.ColorGreen)
drawCell("4", fontBold, 4, creator.ColorBlack, creator.ColorGreen)
// Colspan 3 + 2.
drawCell("3", fontBold, 3, creator.ColorWhite, creator.ColorRed)
drawCell("2", fontBold, 2, creator.ColorWhite, creator.ColorRed)
// Colspan 1 + 2 + 2.
drawCell("1", fontBold, 2, creator.ColorBlack, creator.ColorYellow)
drawCell("2", fontBold, 2, creator.ColorBlack, creator.ColorYellow)
drawCell("2", fontBold, 1, creator.ColorBlack, creator.ColorYellow)
// Colspan 1 + 1 + 1 + 2.
drawCell("2", fontBold, 1, creator.ColorWhite, creator.ColorBlue)
drawCell("1", fontBold, 1, creator.ColorWhite, creator.ColorBlue)
drawCell("1", fontBold, 1, creator.ColorWhite, creator.ColorBlue)
drawCell("2", fontBold, 2, creator.ColorWhite, creator.ColorBlue)
ch.Add(table)
// Draw chapter.
if err := c.Draw(ch); err != nil {
return err
}
return nil
}
Here, you have a single columnSpan() method that has an inline function of drawCell() that accepts a custom number of column spans. This allows you to create cells that can span multiple columns.
However, it should be taken care of here that the columns do not exceed the total number of columns of the table, which in this case were five.
Having a cell span multiple columns is pretty simple, you just call the method table.MultiColCell(colspan), where colspan is an integer that denotes the number of columns to span a cell.
Create Sub-Tables within Tables
In real life, there is hardly any data that can be completely compressed into a two-dimensional table. You usually require more dimensions to amply describe the complex data.
This is where creating sub-tables within a table comes into play. This functionality allows you to increase the complexity of your table to easily integrate data that is too complex for a simple table. The following playground examples deal with the problem and uses UniPDF to create sub-tables within a table.
/*
* This example showcases PDF tables features using unipdf's creator package.
* The output is saved as unipdf-subtables-tables.pdf which illustrates how to
* configure subtables in tables
*/
package main
import (
"fmt"
"log"
"os"
"github.com/unidoc/unipdf/v3/common/license"
"github.com/unidoc/unipdf/v3/creator"
"github.com/unidoc/unipdf/v3/model"
)
func main() {
c := creator.New()
c.SetPageMargins(50, 50, 50, 50)
// Create report fonts.
// UniPDF supports a number of font-families, which can be accessed using model.
font, err := model.NewStandard14Font(model.HelveticaName)
if err != nil {
log.Fatal(err)
}
fontBold, err := model.NewStandard14Font(model.HelveticaBoldName)
if err != nil {
log.Fatal(err)
}
// Create Column Span example
if err := subtables(c, font, fontBold); err != nil {
log.Fatal(err)
}
// Write to output file.
if err := c.WriteToFile("unipdf-subtables-tables.pdf"); err != nil {
log.Fatal(err)
}
}
func subtables(c *creator.Creator, font, fontBold *model.PdfFont) error {
// Create subchapter.
ch := c.NewChapter("Subtables")
ch.SetMargins(0, 0, 30, 0)
ch.GetHeading().SetFont(font)
ch.GetHeading().SetFontSize(13)
ch.GetHeading().SetColor(creator.ColorRGBFrom8bit(72, 86, 95))
// Create subchapter description.
desc := c.NewStyledParagraph()
desc.SetMargins(0, 0, 10, 0)
desc.Append("Large tables can be tedious to construct. In order to make the process more manageable, the table component allows building tables from subtables. If subtables do not fit in the current configuration of the table, the table is automatically expanded.")
ch.Add(desc)
// Create table.
table := c.NewTable(6)
table.SetMargins(0, 0, 10, 0)
headerColor := creator.ColorRGBFrom8bit(255, 255, 0)
footerColor := creator.ColorRGBFrom8bit(0, 255, 0)
generateSubtable := func(rows, cols, index int, rightBorder bool) *creator.Table {
subtable := c.NewTable(cols)
// Add header row.
sp := c.NewStyledParagraph()
sp.Append(fmt.Sprintf("Header of subtable %d", index)).Style.Font = font
cell := subtable.MultiColCell(cols)
cell.SetContent(sp)
cell.SetBorder(creator.CellBorderSideAll, creator.CellBorderStyleSingle, 1)
cell.SetHorizontalAlignment(creator.CellHorizontalAlignmentCenter)
cell.SetVerticalAlignment(creator.CellVerticalAlignmentMiddle)
cell.SetBackgroundColor(headerColor)
for i := 0; i < rows; i++ {
for j := 0; j < cols; j++ {
sp = c.NewStyledParagraph()
sp.Append(fmt.Sprintf("%d-%d", i+1, j+1))
cell = subtable.NewCell()
cell.SetContent(sp)
if j == 0 {
cell.SetBorder(creator.CellBorderSideLeft, creator.CellBorderStyleSingle, 1)
}
if rightBorder && j == cols-1 {
cell.SetBorder(creator.CellBorderSideRight, creator.CellBorderStyleSingle, 1)
}
}
}
// Add footer row.
sp = c.NewStyledParagraph()
sp.Append(fmt.Sprintf("Footer of subtable %d", index))
cell = subtable.MultiColCell(cols)
cell.SetContent(sp)
cell.SetBorder(creator.CellBorderSideAll, creator.CellBorderStyleSingle, 1)
cell.SetHorizontalAlignment(creator.CellHorizontalAlignmentCenter)
cell.SetVerticalAlignment(creator.CellVerticalAlignmentMiddle)
cell.SetBackgroundColor(footerColor)
subtable.SetRowHeight(1, 30)
subtable.SetRowHeight(subtable.Rows(), 40)
return subtable
}
// Add subtable 1 on row 1, col 1 (4x4)
table.AddSubtable(1, 1, generateSubtable(4, 4, 1, false))
// Add subtable 2 on row 1, col 5 (4x4)
// Table will be expanded to 8 columns because the subtable does not fit.
table.AddSubtable(1, 5, generateSubtable(4, 4, 2, true))
// Add subtable 3 on row 7, col 1 (4x4)
table.AddSubtable(7, 1, generateSubtable(4, 4, 3, false))
// Add subtable 4 on row 7, col 5 (4x4)
table.AddSubtable(7, 5, generateSubtable(4, 4, 4, true))
// Add subtable 5 on row 13, col 3 (4x4)
table.AddSubtable(13, 3, generateSubtable(4, 4, 5, true))
// Add subtable 6 on row 13, col 1 (3x2)
table.AddSubtable(13, 1, generateSubtable(3, 2, 6, false))
// Add subtable 7 on row 13, col 7 (3x2)
table.AddSubtable(13, 7, generateSubtable(3, 2, 7, true))
// Add subtable 8 on row 18, col 1 (3x2)
table.AddSubtable(18, 1, generateSubtable(3, 2, 8, false))
// Add subtable 9 on row 19, col 3 (2x4)
table.AddSubtable(19, 3, generateSubtable(2, 4, 9, true))
// Add subtable 10 on row 18, col 7 (3x2)
table.AddSubtable(18, 7, generateSubtable(3, 2, 10, true))
ch.Add(table)
// Draw chapter.
if err := c.Draw(ch); err != nil {
return err
}
return nil
}
In the playground example, you have the user defined subtables()
method that creates the chapter of subtables in the PDF document.
The generateSubtable()
method has been defined, which accepts rows, columns, index of table and a boolean of right border as parameters. The boolean defines whether the sub-table should have a right border or not.
At the end, you use the inbuilt method of table.AddSubtable()
that accepts the rows and columns and a table in its parameters. In the example, there is code for five sub-tables, however, sub-table five has been commented out.
You can see the void it leaves in the document visible in the playground, uncomment it and see how it is placed adjacent to table three.
Conclusion
Working with tables can be fun, or it can be a challenge. It all depends on which tools you are using to make the tables. UniPDF provides a powerful interface for you to create tables in PDF documents using the power of pure golang. It is quite simple, as noticeable from the playground examples, to create tables using UniPDF.
Feel free to create new examples using the playground, for starters, uncomment the code for sub-table five and run it to see changes in real time.
Since UniPDF is constantly being updated to add new features that our customers request, you should subscribe to our mailing list to keep up to date with the latest news.