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:
| Field | Holds | Used in markup as |
|---|---|---|
ColorMap | creator.Color values | color="primary" |
ImageMap | pre-loaded *model.Image | <image src="qr-code-1"> |
FontMap | pre-loaded *model.PdfFont | font="arial-bold" |
ChartMap | render.ChartRenderable values | <chart src="pie-chart-1"> |
HelperFuncMap | Go functions | {{formatTime .Issued "02 Jan"}} |
SubtemplateMap | other 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.
| Reference | Not 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 tag | Skipped, 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:
| Helper | Does |
|---|---|
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 c | Builds a slice. |
add x y | Adds two numbers, int or float. |
makeSeq start step end | Builds 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 
 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
| Guide | Covers |
|---|---|
| Airplane ticket | Driving a shared cell subtemplate from one extendDict property map, plus a QR code through ImageMap. |
| Aviation checklist | Measuring content in Go to decide layout, and emitting unbalanced markup to break into a new column. |
| Bank account statement | Subtemplates with defaults, helpers that generate form furniture, and a header carrying page numbers. |
| Boarding pass | One {{define}} block drawn twice for the coupon and the stub, with row spans and a dashed cut line. |
| Concert ticket | Reusing a subtemplate with different arguments, and filtering fields for a second copy. |
| Documentation | A large multi-chapter document: subtemplates per chapter, a generated table of contents, charts and a front page. |
| Lab results | One page per record, and mapping a data enum to a color through a helper. |
| Log book report | Paginating a thousand records in Go, sorted map iteration, and escaping untrusted data. |
| Medical bill | The minimal case: no TemplateOptions at all, standard fonts and path() images. |
| Medication schedule report | Computing column widths from the data, and a wide grid that wraps across pages. |
| Receipt | The smallest template of the set: an A5 page built from a slice of label/value pairs. |
| Rental agreement | A long document broken up with page breaks, text helpers for legal prose, and measured rule lines. |
| Security report | Charts built by helper functions and resolved through ChartMap, with chapters and a table of contents. |
| Trade confirmation | A statement made entirely of nested tables, with no registered resources. |