2 minutes
Generating PDFs with Go
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.
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
.
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:
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)
}
385 Words
2021-09-13