Generating Dynamic PDF Reports from Given Data: Report Cards
Tags: programming, go, productivity, reporting
Published: 2021-06-03 10:59:14


**Introduction**
It is useful to generate reports in PDF format so as they are not modifiable and can also be printed easily. Here we are going to talk about how to generate reports using UniPDF in the form of tabular data. We will be looking at it through an example of generating Individual report cards for students from a given set of data. 

First we will be looking at generating data. Here we will be using structs and populating data manually, but we could easily provide the input data as an Excel XLSX file and use UniOffice to load the data.  Perhaps we will add that in the future. Let us know if you are interested.

Then we will look at how UniPDF creates a table and how to insert cells and format the tables and how to insert the relevant data in the cells. After that we will write it all in PDF.


**Generating Data** 
We can use structs to store the data. Here we have defined two structs. One struct to store the marks and subjects and another struct that holds the entire information for the students, like in our example name, marks (which is a struct) and conduct of the students. 

```go
type studentReportCardMark struct {
  Subject string
  Mark int
}

type studentReportCard struct {
  Name string
  Marks []studentReportCardMark
  Punctual rune
  Attentive rune
  Orderly rune
  Polite rune
} 
```
We have made sample data for four students to generate our report cards. For example we will show one here. 
```go
var reportCards = []studentReportCard{
{
 Name: "Mike",
 Marks: []studentReportCardMark{
 {
 Subject: "Chemistry 101",
 Mark: 21,
 },
 {
 Subject: "Physics 101",
 Mark: 13,
 },
 {
 Subject: "English 101",
 Mark: 50,
 },
 {
 Subject: "Science 101",
 Mark: 23,
 },
 {
 Subject: "Biology 101",
 Mark: 26,
 },
 {
 Subject: "Computers 101",
 Mark: 33,
 },
 {
 Subject: "Tech 101",
 Mark: 34,
 },
 {
 Subject: "BioChem 101",
 Mark: 14,
 },
 },
 Punctual: 'C',
 Attentive: 'B',
 Orderly: 'B',
 Polite: 'A',
 }
}
``` 

Report Design
To design the entire report card we have used tables from UniPDF. For tables we have defined a map of cellStyles that holds the data for the different types of cells we wanna generate in our tables for this design. It has been implemented via struct that holds the properties for individual cells.  This is pretty convenient and easy to add and change styles. 

```go
type cellStyle struct {
 ColSpan int
 HAlignment creator.CellHorizontalAlignment
 BackgroundColor creator.Color
 BorderSide creator.CellBorderSide
 BorderStyle creator.CellBorderStyle
 BorderWidth float64
 BorderColor creator.Color
 Indent float64
} 
``` 
One such example of a cellStyle used in this design is used to write the heading to the left of the cell. 
```go
var cellStyles = map[string]cellStyle{
 "heading-left": {
 BackgroundColor: creator.ColorRGBFromHex("#332f3f"),
  HAlignment: creator.CellHorizontalAlignmentLeft,
 BorderColor: creator.ColorWhite,
 BorderSide: creator.CellBorderSideAll,
 BorderStyle: creator.CellBorderStyleSingle,
 BorderWidth: 6,
 }
```
Similarly the report card has used different cellStyles to generate the cells which are described below. 

**Header**
For the header we have used a table of 1 cell with center aligned position and have used a rectangle to highlight the background of the Report Card Heading. It looks like this. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/20gasajez1k7dmrsiql9.png) 

The code is as follows.  And it also includes two lines at the end which were drawn using a newLine function. 
```go
// Filled rectangle on top. 
rect := c.NewRectangle(0, 0, creator.PageSizeLetter[0], 120)
rect.SetFillColor(creator.ColorRGBFromHex("#dde4e5"))
rect.SetBorderWidth(0)
c.Draw(rect)

headerStyle := c.NewTextStyle()
headerStyle.FontSize = 50

// Table with 1 column to center the text.
table := c.NewTable(1)
table.SetMargins(0, 0, 20, 0)
drawCell(table, newPara(c, "Report Card", headerStyle), cellStyles["centered"])
c.Draw(table)

// Double line.
line := c.NewLine(0, 120, creator.PageSizeLetter[0], 120)
line.SetLineWidth(5)
c.Draw(line)

line = c.NewLine(0, 123, creator.PageSizeLetter[0], 123)
line.SetLineWidth(1.5)
c.Draw(line)
```
**Generating a Student Description**
Similarly using a 3 column table a description was generated for each student using the data that was added in the sample and some generic data for the rest of the columns.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7tcrfv27qpnhoaixs2q7.png)

This was done using the drawCell function which was designed to draw individual cells as per our need in the given table. 
```go
drawCell(table, newPara(c, "Student Name: "+card.Name, studentInfoStyle), cellStyles["left"])

drawCell(table, newPara(c, "School Year: 2020", studentInfoStyle), cellStyles["left"])

drawCell(table, newPara(c, "Roll No: 320", studentInfoStyle), cellStyles["left"])

drawCell(table, newPara(c, "Date: 14/12/2020", studentInfoStyle), cellStyles["left"])

drawCell(table, newPara(c, "Department: Bachelors of Arts", studentInfoStyle), cellStyles["left"])
```
**The Grade Table with Individual Subject Scores**
The Grade Table was generated using the sample data and some functions were used to generate the grades and then add them to the table for individual subjects.  The subject is left aligned and the rest of the columns are center aligned.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ea0hyyfff1xitbusxe1a.png)

The code is as follows 
```go
for _, mark := range card.Marks {
 drawCell(table, newPara(c, " "+mark.Subject, regularStyle), cellStyles["left-highlighted"])
 s := mark.Mark
 percent := (float64(s) / 50.0) * 100.0
 drawCell(table, newPara(c, fmt.Sprintf("%d", s), regularStyle), cellStyles["centered-highlighted"])
 drawCell(table, newPara(c, fmt.Sprintf("%.0f%%", percent), regularStyle), cellStyles["centered-highlighted"])
 drawCell(table, newPara(c, calcGrade(percent), regularStyle), cellStyles["centered-highlighted"])
}
```
**Grading Scheme and Conduct Column**
The rest of the report consists of the grading scheme and the conduct which was again generated using the tables and formatted using the cell Style struct that we defined earlier. The Grading scheme is a one column table that displays how grades were assigned and the conduct is a two column table to define conduct for each student. The alignment of these tables was done with a 12-column alignment grid table where the grade and conduct tables were placed at column column 1 and 8 respectively, spawning 3 and 5 columns each. 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f4sjcwyx6omamyyzvy44.png)

```go
 // 12-column table for alignment (grid).
 grid := c.NewTable(12)
 grid.SetMargins(0, 0, 50, 0)

 gradeInfoStyle := c.NewTextStyle()
 gradeInfoStyle.Font = font
 gradeInfoStyle.FontSize = 15

 // Grading system table.
 table = c.NewTable(1)
 drawCell(table, newPara(c, "Grading System:", boldStyle), cellStyles["gradingsys-head"])
 drawCell(table, newPara(c, "\u2022 A > 80%", gradeInfoStyle), cellStyles["gradingsys-row"])
 drawCell(table, newPara(c, "\u2022 B > 70%", gradeInfoStyle), cellStyles["gradingsys-row"])
 drawCell(table, newPara(c, "\u2022 C > 60%", gradeInfoStyle), cellStyles["gradingsys-row"])
 drawCell(table, newPara(c, "\u2022 D > 50%", gradeInfoStyle), cellStyles["gradingsys-row"])
 drawCell(table, newPara(c, "\u2022 F < 50%", gradeInfoStyle), cellStyles["gradingsys-row"])

 grid.MultiColCell(3).SetContent(table)
 grid.SkipCells(4)

 // Conduct table.
 table = c.NewTable(2)
 table.SetColumnWidths(0.6, 0.4)
 drawCell(table, newPara(c, "Conduct:", boldStyle), cellStyles["conduct-head"])
 table.SkipCells(1)
 drawCell(table, newPara(c, "Punctual:", gradeInfoStyle), cellStyles["conduct-key"])
 drawCell(table, newPara(c, string(card.Punctual), gradeInfoStyle), cellStyles["conduct-val"])
 drawCell(table, newPara(c, "Attentive:", gradeInfoStyle), cellStyles["conduct-key"])
 drawCell(table, newPara(c, string(card.Attentive), gradeInfoStyle), cellStyles["conduct-val"])
 drawCell(table, newPara(c, "Orderly:", gradeInfoStyle), cellStyles["conduct-key"])
 drawCell(table, newPara(c, string(card.Orderly), gradeInfoStyle), cellStyles["conduct-val"])
 drawCell(table, newPara(c, "Polite:", gradeInfoStyle), cellStyles["conduct-key"])
 drawCell(table, newPara(c, string(card.Polite), gradeInfoStyle), cellStyles["conduct-val"])

 grid.MultiColCell(5).SetContent(table)

 c.Draw(grid)
```
**Generating the PDF output file**
In the end the PDF can be generated by using the WriteToFile function which was used in the generateReports function. 
```go
// Write to output file.
if err := c.WriteToFile(card.Name + "_marksheet.pdf"); err != nil {
 log.Fatal(err)
}
```
The whole function
```go
func generateReports(font, fontBold *model.PdfFont, cards []studentReportCard) error {
 for _, card := range cards {
 // Creating Reports
 c := creator.New()
 c.SetPageMargins(40, 40, 0, 0)

 // Filled rectangle on top.
 rect := c.NewRectangle(0, 0, creator.PageSizeLetter[0], 120)
 rect.SetFillColor(creator.ColorRGBFromHex("#dde4e5"))
 rect.SetBorderWidth(0)
 c.Draw(rect)

  headerStyle := c.NewTextStyle()
 headerStyle.FontSize = 50

 // Table with 1 column to center the text.
 table := c.NewTable(1)
 table.SetMargins(0, 0, 20, 0)
 drawCell(table, newPara(c, "Report Card", headerStyle), cellStyles["centered"])
 c.Draw(table)

 // Double line.
 line := c.NewLine(0, 120, creator.PageSizeLetter[0], 120)
 line.SetLineWidth(5)
 c.Draw(line)

 line = c.NewLine(0, 123, creator.PageSizeLetter[0], 123)
 line.SetLineWidth(1.5)
 c.Draw(line)

 // writing Marks
 writeMarks(c, font, fontBold, card)

 // Line on bottom.
 line = c.NewLine(0, creator.PageSizeLetter[1]-3, creator.PageSizeLetter[0], creator.PageSizeLetter[1]-3)
 line.SetLineWidth(5)
 c.Draw(line)

 line = c.NewLine(0, creator.PageSizeLetter[1], creator.PageSizeLetter[0], creator.PageSizeLetter[1])
 line.SetLineWidth(1.5)
 c.Draw(line)

 // Write to output file.
 if err := c.WriteToFile(card.Name + "_marksheet.pdf"); err != nil {
 log.Fatal(err)
 }
 }
 return nil
}
```

**Final Report**
The final Report after drawing all the tables and adding the relevant information looks as follows 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/voqst2tkzt67ax9w2x99.png)

**Performance**
Lastly, we want to check the performance of our report generation. We add to the beginning of our code 
```go
func main() {
    start := time.Now()
```
and at the end of the main function
```go
    end := time.Now()
    fmt.Printf("diff: %v\n", end.Sub(start))
}
```
This gives us the total time for generating all the 5 reports for 5 students.  The result we get is 
```go
diff: 36.193497ms
```
So around 36.2ms, or 7.2ms.  This means in one second, we could generate hundreds of reports, or in a web system we could serve PDF reports in real-time with a click of a button.

**Playground Example: Full Student Report Card**
The full code and output on our Playground where it can be edited and re-generated as desired. 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9sl9zlnqokb4ab6melz5.png) 
https://play.unidoc.io/p/98b800fa3f735b61

**Conclusion**
UniPDF can be used to generate professional reports with little effort and easy to learn with superb performance. There are many things that can be done in this way like generating Financial Reports, Public data, Medical Health Records, UniPDF can provide the base for generating a lot of reports in a little time.