Creating PDF Templates for Dynamic Content in Golang

As a Golang user, you may often come across the need to generate PDFs with dynamic content. Whether you’re building a web application or working on a data-driven project, being able to create PDF templates that adapt to changing content is essential.

In this blog post, we will explore how to achieve this using Golang.

1. Understanding the Importance of PDF Templates

PDF templates play a crucial role in generating consistent and professional-looking documents. They provide a structured layout that can be populated with dynamic data, allowing you to automate the creation of documents such as invoices, reports, or certificates. By leveraging PDF templates, you can save time, ensure accuracy, and enhance the overall user experience.

2. Leveraging Golang’s Templating Engine

Golang provides a powerful templating engine that makes it easy to create dynamic PDF templates. The standard html/template package allows you to define your templates using HTML-like syntax and inject dynamic data into them. This approach not only simplifies the process but also enables you to reuse existing HTML templates if you have them.

To begin, import the necessary packages:

import (
    "bytes"
    "encoding/json"
    "html/template"
    "io"
    "log"
    "os"

    "github.com/unidoc/unipdf/v3/common/license"
    "github.com/unidoc/unipdf/v3/creator"
)

Next, define your template by creating an HTML file with placeholders for dynamic content. For example, let’s say we want to generate an invoice PDF. We can create an invoice.html file with the following content:

{{define "invoice"}}
<list-item>
    <list-marker font-size="15">{{ .InvoiceIndex }}.</list-marker>

    <paragraph>
        <text-chunk font-size="15"
            >{{ printf "Invoice Client: %s" .Invoice.Client }}</text-chunk
        >
    </paragraph>
</list-item>

<list-item>
    <list indent="0">
        {{range $idx, $item := .Invoice.Item}} {{template "item" (dict
        "ItemIndex" (printf "%d %d" $.InvoiceIndex (add $idx 1)) "Item" $item )
        }} {{end}}
    </list>
</list-item>
{{end}} {{define "item"}}
<list-marker>- </list-marker>
<list-item>
    <list indent="0">
        <list-marker> </list-marker>
        <list-item>
            <paragraph>
                <text-chunk>Item Name: {{ .Item.Name }}</text-chunk>
            </paragraph>
        </list-item>
        <list-item>
            <paragraph>
                <text-chunk>Quantity: {{ .Item.Quantity }}</text-chunk>
            </paragraph>
        </list-item>
        <list-item>
            <paragraph>
                <text-chunk>Price: {{ .Item.Price }}</text-chunk>
            </paragraph>
        </list-item>
    </list>
</list-item>
{{end}} {{range $idx, $invoice := .Invoice}}
<list>
    {{template "invoice" (dict "InvoiceIndex" (add $idx 1) "Invoice" $invoice )
    }}
</list>
{{end}}

In this template, we utilize double curly braces {{}} as placeholders for dynamic data, with the . operator signifying the current context or data being injected.

The template aims to construct a structured representation of invoices and their corresponding items. It introduces two primary sections: “invoice” and “item.”

The “invoice” segment delineates an invoice, encompassing elements such as the invoice index and a list of associated items.

On the other hand, the “item” section portrays an individual item within an invoice, detailing attributes like the item’s name, quantity, and price.

The template then iterates through a range of invoices, employing these sections to produce a structured document, making it well-suited for generating documents like invoices or reports from organized data.

3. Generating Dynamic Content

Now that you have an understanding of how to create PDF templates in Golang, let’s explore how to generate dynamic content for these templates.

Assuming we have a struct representing an invoice:

package main

import (
    "bytes"
    "encoding/json"
    "html/template"
    "io"
    "log"
    "os"

    "github.com/unidoc/unipdf/v3/common/license"
    "github.com/unidoc/unipdf/v3/creator"
)

func init() {
    // Make sure to load your metered License API key prior to using the library.
    // If you need a key, you can sign up and create a free one at https://cloud.unidoc.io.
    err := license.SetMeteredKey(os.Getenv(`UNIDOC_LICENSE_API_KEY`))
    if err != nil {
        panic(err)
    }
}

// Struct to hold item data in the invoice.
type Item struct {
    Name     string `json:"name"`
    Quantity string `json:"quantity"`
    Price    string `json:"price"`
}

// Struct to represent an invoice with client and items.
type Invoice struct {
    Client string `json:"client"`
    Item   []Item `json:"item"`
}

// Struct to hold the entire report, including multiple invoices.
type Report struct {
    Invoice []Invoice `json:"invoice"`
}

func main() {
    // Create a new PDF creator instance.
    c := creator.New()
    c.SetPageMargins(15, 15, 20, 20)
    c.SetPageSize(creator.PageSizeA5)

    // Read the main content template from a file.
    tpl, err := readTemplate("./htmlTemplate.tpl")
    if err != nil {
        log.Fatal(err)
    }

    // Read receipt data from a JSON file.
    data, err := readData("./data.json")
    if err != nil {
        panic(err)
    }

    // Template options, including a helper function for calculations.
    tplOpts := &creator.TemplateOptions{
        HelperFuncMap: template.FuncMap{
            "add": func(a, b int) int {
                return a + b
            },
        },
    }

    // Draw the content template with the provided data.
    if err := c.DrawTemplate(tpl, data, tplOpts); err != nil {
        log.Fatal(err)
    }

    // Write the resulting PDF to an output file.
    if err := c.WriteToFile("unipdf-template.pdf"); err != nil {
        log.Fatal(err)
    }
}

// readTemplate reads a template file and returns an io.Reader.
func readTemplate(tplFile string) (io.Reader, error) {
    file, err := os.Open(tplFile)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    buf := bytes.NewBuffer(nil)
    if _, err = io.Copy(buf, file); err != nil {
        return nil, err
    }

    return buf, nil
}

// readData reads a JSON file containing invoice data and returns a Report struct.
func readData(jsonFile string) (*Report, error) {
    file, err := os.Open(jsonFile)
    if err != nil {
        return nil, err
    }

    defer file.Close()

    report := &Report{}
    if err := json.NewDecoder(file).Decode(report); err != nil {
        return nil, err
    }

    return report, nil
}

By providing a structured data source, such as a struct or a map, you can easily populate the template with dynamic content. This flexibility allows you to generate PDFs tailored to different scenarios without having to modify the underlying template structure.

For example, this data can be used to check the results:

{
    "invoice": [
        {
            "client": "Acme Corporation",
            "item": [
                {
                    "name": "product A",
                    "quantity": "2",
                    "price": "19.99"
                },
                {
                    "name": "product A2",
                    "quantity": "22",
                    "price": "10"
                }
            ]
        },
        {
            "client": "2 Corporation",
            "item": [
                {
                    "name": "product B",
                    "quantity": "3",
                    "price": "9.99"
                }
            ]
        },
        {
            "client": "3 Corporation",
            "item": [
                {
                    "name": "product C",
                    "quantity": "4",
                    "price": "29.99"
                }
            ]
        }
    ]
}

Conclusion

In this blog post, we explored how to create PDF templates for dynamic content in Golang. By leveraging Golang’s templating engine, you can easily generate professional-looking PDFs that adapt to changing data. With the ability to automate the creation of documents like invoices or reports, you can enhance productivity and improve user experiences. Start implementing PDF templates in your Golang projects today and unlock the power of dynamic content generation.

Generated PDF invoice template

FAQ’s

Can I use existing HTML templates for generating PDFs in Golang?

Yes, Golang’s templating engine allows you to reuse existing HTML templates by making minor modifications to include placeholders for dynamic content. This enables you to leverage your existing knowledge and resources effectively.

How can I customize the styling of the generated PDF?

Golang’s templating engine primarily focuses on generating the content of the PDF. To style the generated PDF, you can use CSS styling within your HTML templates. By defining styles inline or using external CSS files, you can achieve the desired look and feel.

Are there any performance considerations when generating PDFs using Golang?

While Golang’s templating engine is efficient, generating large PDFs with complex templates or extensive data might impact performance. It is important to monitor resource usage and optimize your code accordingly. Consider pagination techniques or breaking down large PDFs into smaller chunks if necessary.