Skip to content
Templates

Templates

A creator template is an XML document describing creator components. You keep the layout in a .tpl file, supply data and resources from Go, and the creator turns the markup into paragraphs, tables, images and charts at run time. The point is that layout changes no longer mean recompiling: as long as the data shape is unchanged, you can edit the .tpl file and rerun.

One call draws a template onto the page:

err := c.DrawTemplate(tplReader, data, tplOpts)

Headers, footers and any other block-based content use the Block method instead, which takes the creator as its first argument:

err := block.DrawTemplate(c, tplReader, data, tplOpts)

Both data and options may be nil. Several of the examples here pass nil for options, because a document made of standard fonts and path() images needs no registered resources at all.

Two phases

Drawing a template runs two passes, and nearly every technique on these pages depends on knowing which pass does what.

First the file is executed as a Go text/template. {{.Name}}, {{range}}, {{if}} and any helper functions run here, and the result is a string. Only then is that string parsed as XML and translated into creator components.

So the markup you write is not what gets parsed. It is the text your template produced. That has three consequences worth remembering:

Tag balance only has to hold in the output. A branch may emit a bare </table-cell> and its counterpart may appear in a different branch, which is how the aviation checklist starts a new column mid-loop.

Helper functions have finished running before any resource is looked up. A helper can therefore build a chart, put it in the ChartMap and return its name, and the name resolves later in the XML pass. The security report does exactly that.

Data has to be valid XML text. An & or < in a data value breaks the parse, so values that could contain them get escaped in the template, either with Go’s built-in html function or with a helper wrapping template.HTMLEscaper.

Registering resources

creator.TemplateOptions carries six maps. Everything in them is addressed from the markup by the key you chose, never by file path or Go variable name:

FieldHoldsUsed in markup as
ColorMapcreator.Color valuescolor="primary"
ImageMappre-loaded *model.Image<image src="qr-code-1">
FontMappre-loaded *model.PdfFontfont="arial-bold"
ChartMaprender.ChartRenderable values<chart src="pie-chart-1">
HelperFuncMapGo functions{{formatTime .Issued "02 Jan"}}
SubtemplateMapother templates, keyed by name{{template "chapter-1" .}}

Images and fonts can also come straight off disk, without being registered:

<image src="path('templates/res/logo.png')" fit-mode="fill-width"></image>
<text-chunk font="path('templates/res/Exo-Bold.ttf')">Heading</text-chunk>

path() is the easier route for a logo used once. ImageMap and FontMap are worth it for anything drawn repeatedly or on every page, since a registered resource is loaded once rather than per reference.

Build the options value once and share it. Every drawing pass that should look consistent - body, header, footer, front page - needs the same options, and a header drawn with nil options silently loses access to your fonts and colors.

What happens when a name is wrong

Resource lookups do not fail uniformly, which matters when you are debugging a template that renders but looks wrong.

ReferenceNot found
color="..."Falls back to black. No error.
font="..."Falls back to the creator’s default regular font. No error.
<image src="...">DrawTemplate returns an “invalid template resource” error.
<chart src="...">DrawTemplate returns an “invalid template resource” error.
unknown tagSkipped, with a debug-level log line only.

A color name is checked against ColorMap first and then treated as a hex code if it starts with #, so color="primry" is a miss and comes out black. A font name is checked against FontMap first and then against the standard 14 names (helvetica, times-bold, zapf-dingbats and so on), so a misspelled key quietly reverts to Helvetica at the same size.

Putting a tag inside a parent that does not accept it is a hard error, “invalid template parent node”, and it aborts the whole draw. <page-break> is only valid directly under the creator or inside a <chapter>; <background> only inside a <division>; <table-cell> only inside a <table>.

Text between tags is used only by <text-chunk> and <chapter-heading>. Anywhere else it is discarded, which is why indentation and blank lines in the markup are harmless.

Built-in helpers

Five functions are always available, and HelperFuncMap cannot override them - an entry with a colliding name is skipped:

HelperDoes
dict "K" v ...Builds a map[string]interface{} from alternating key/value pairs.
extendDict $m "K" v ...Adds pairs to an existing map.
array a b cBuilds a slice.
add x yAdds two numbers, int or float.
makeSeq start step endBuilds an inclusive []int.

dict is how arguments get passed to a subtemplate, since {{template}} accepts only one value:

{{template "simple-paragraph" dict "Font" "helvetica-bold" "FontSize" 12 "Text" .Title}}

Two things about extendDict catch people out. It mutates the map you give it and returns that same map, so a key set on one call is still set on the next - the airplane ticket relies on this and has to reset attributes it does not want carried forward. And a key the subtemplate reads but the dict does not have renders as the literal string <no value>, which then lands inside an XML attribute. Either pass every key the subtemplate reads, or give the subtemplate defaults, as the bank account statement does.

Headers, footers and page count

c.DrawHeader and c.DrawFooter take a callback that receives a *creator.Block and gets called once per page. The block is exactly as tall as the corresponding page margin: NewBlock(pageWidth, pageMargins.Top) for the header and NewBlock(pageWidth, pageMargins.Bottom) for the footer. A header template taller than the top margin is clipped, so reserving room for it is a SetPageMargins call, not a template setting. That is why lab results sets a 180 point top margin for a header carrying a logo and six info fields.

HeaderFunctionArgs and FooterFunctionArgs provide PageNum and TotalPages, which you pass on as ordinary template data. In v5 they also carry a Chapter *ChapterInfo describing the chapter the page belongs to, nil on pages before the first chapter.

Both callbacks run for every page including the first, so a running header that should not appear on a cover page has to return early:

c.DrawHeader(func(block *creator.Block, args creator.HeaderFunctionArgs) {
    if args.PageNum == 1 {
        return
    }
    // read and draw the header template
})

Line breaks in text

There is no line break tag. A newline has to arrive as character data, and the examples use three different routes to get one: the XML entity &#xA; written directly in the markup, {{printf "\n"}} from the template, or a \n embedded in a data value by printf "%s\n%s" in Go.

Where to look

GuideCovers
Airplane ticketDriving a shared cell subtemplate from one extendDict property map, plus a QR code through ImageMap.
Aviation checklistMeasuring content in Go to decide layout, and emitting unbalanced markup to break into a new column.
Bank account statementSubtemplates with defaults, helpers that generate form furniture, and a header carrying page numbers.
Boarding passOne {{define}} block drawn twice for the coupon and the stub, with row spans and a dashed cut line.
Concert ticketReusing a subtemplate with different arguments, and filtering fields for a second copy.
DocumentationA large multi-chapter document: subtemplates per chapter, a generated table of contents, charts and a front page.
Lab resultsOne page per record, and mapping a data enum to a color through a helper.
Log book reportPaginating a thousand records in Go, sorted map iteration, and escaping untrusted data.
Medical billThe minimal case: no TemplateOptions at all, standard fonts and path() images.
Medication schedule reportComputing column widths from the data, and a wide grid that wraps across pages.
ReceiptThe smallest template of the set: an A5 page built from a slice of label/value pairs.
Rental agreementA long document broken up with page breaks, text helpers for legal prose, and measured rule lines.
Security reportCharts built by helper functions and resolved through ChartMap, with chapters and a table of contents.
Trade confirmationA statement made entirely of nested tables, with no registered resources.
Last updated on