Developers , Uncategorized

Generating PDFs with Go

How to generate pdfs with Golang

I recently had to work on a Go project where PDFs had to be generated, with custom data, based on a template.

After considering a few options, I decided to go with wkhtmltopdf’s wrapper github.com/SebastiaanKlippert/go-wkhtmltopdf, since this a solid and widely used open source library and I would only be using a Go layer on top of it.

Also, this would allow the template to be maintained in HTML, which would hopefully end up in less headaches than creating PDF’s with a lower level generator kind of thing.

Cut to the chase

Not directly related… But I defined a interface for this service so it could be mocked, and so on.

JavaScript
package domain

// PDFService represents the interface of a pdf generation service
type PDFService interface {
	GeneratePDF(data *SomeModel) ([]byte, error)
}

Here’s the service itself. It quite self explanatory but still, I’ve added comments on meaningful steps.

It essentially consists of first generating an HTML file based on a template and then pass that HTML to wkhtmltopdf.

JavaScript
package pdf

import (
	"bytes"
	"github.com/SebastiaanKlippert/go-wkhtmltopdf"
	"html/template"
	domain "the-project"
)

type PDFService struct {}

func NewPDFService() *PDFService {
	return &PDFService{}
}

func (p PDFService) GeneratePDF(data *domain.SomeModel) ([]byte, error) {
	var templ *template.Template
	var err error

	// use Go's default HTML template generation tools to generate your HTML
	if templ, err = template.ParseFiles("pdf-template.html"); err != nil {
		return nil, err
	}

	// apply the parsed HTML template data and keep the result in a Buffer
	var body bytes.Buffer
	if err = templ.Execute(&body, data); err != nil {
		return nil, err
	}

	// initalize a wkhtmltopdf generator
	pdfg, err := wkhtmltopdf.NewPDFGenerator()
	if err != nil {
		return nil, err
	}

	// read the HTML page as a PDF page
	page := wkhtmltopdf.NewPageReader(bytes.NewReader(body.Bytes()))

	// enable this if the HTML file contains local references such as images, CSS, etc.
	page.EnableLocalFileAccess.Set(true)

	// add the page to your generator
	pdfg.AddPage(page)

	// manipulate page attributes as needed
	pdfg.MarginLeft.Set(0)
	pdfg.MarginRight.Set(0)
	pdfg.Dpi.Set(300)
	pdfg.PageSize.Set(wkhtmltopdf.PageSizeA4)
	pdfg.Orientation.Set(wkhtmltopdf.OrientationLandscape)

	// magic
	err = pdfg.Create()
	if err != nil {
		return nil, err
	}

	return pdfg.Bytes(), nil
}

Extra

Say you want to serve the PDF in an API response:

JavaScript
func (h *YourHandler) GetPDF(w http.ResponseWriter, r *http.Request) {
	// .....

	pdfBytes, err := h.pdfService.GeneratePDF(&data)
	if err != nil {
		httputil.RespondInternalError(w, r, err)
		return
	}

	w.Header().Set("Content-Disposition", "attachment; filename=kittens.pdf")
	w.Header().Set("Content-Type", "application/pdf")
	w.WriteHeader(http.StatusOK)
	w.Write(pdfBytes)
}

Author: Diogo Simões, Tech Lead at Cloudoki.

Related Articles

AI Developers

LLMs, the gist

Unlock the magic of Large Language Models (LLMs) with our comprehensive guide. Discover ho...

Read more
AI Developers

Demystifying Artificial Intelligence: The gist of it

Gain valuable insights into Artificial Intelligence with our Head of Development, Rui Mole...

Read more
AI Blockchain Developers Web3

Future Forward: Blockchain, Web3, and AI Technology with Doğukan Akkaya

Join Doğukan Akkaya as he navigates the cutting edge of technology in 'Future Forward.' F...

Read more
About us Developers

Beyond Code: David Rosario’s Rise in Project Management 

Explore the insightful journey of David Rosario, transitioning from a skilled developer to...

Read more