xml2pdf Playground Source

Guide

How xml2pdf documents are put together: page setup, how content flows and breaks across pages, styling, and templating with variables and loops.

Document structure

A document is a <document> root containing, in any order:

<document title="My report" page-size="A4" margin="20mm" font="helvetica" size="11">
  <constants>...</constants>     <!-- named values usable as ${name} -->
  <resources>...</resources>     <!-- images and named styles -->
  <watermark .../>               <!-- zero or more -->
  <header>...</header>           <!-- optional, repeated on pages -->
  <footer>...</footer>           <!-- optional, repeated on pages -->
  <page>...</page>               <!-- one or more -->
</document>

Rendering happens in stages:

  1. Parse. The XML is read and every element keeps its line number for error messages.
  2. Expand. ${variables} are substituted and <for-each> loops are unrolled.
  3. Validate. Elements and attributes are checked; unknown names, bad values and missing styles or images are errors.
  4. Lay out. Text is wrapped, tables are sized and content is split into pages.
  5. Render. Headers, footers and watermarks are added and the PDF is written.

Because variables are expanded first, you can use ${...} in any attribute or text. That includes colors, sizes and file names.

Units

A bare number is in points (1/72 inch). Lengths may also have a unit suffix:

UnitExampleEquals
pt (default)12, 12pt1/72 inch
mm20mm2.835 pt
cm2.5cm28.35 pt
in0.75in72 pt
%50%depends on the attribute (see below)

Percentages are allowed on width (a percentage of the available width), height of images (of the content height), x (of the page width), y (of the page height), and table column widths (of the table width). Margins, padding, font sizes, border widths and spacer heights must be absolute.

Page setup

The page size, orientation and margins are set on <document>, and any <page> can override them.

page-sizePortrait size
A3297 × 420 mm
A4 (default)210 × 297 mm
A5148 × 210 mm
Letter8.5 × 11 in
Legal8.5 × 14 in
Tabloid11 × 17 in
WIDTHxHEIGHTcustom, e.g. 100mmx150mm or 400x600

orientation="landscape" swaps the width and height. margin takes 1–4 values in CSS order: "20mm" (all sides), "20mm 15mm" (vertical, horizontal), "10 15 20" (top, horizontal, bottom) or "10 15 20 25" (top, right, bottom, left). The default margin is 0.5in.

<document page-size="Letter" margin="0.75in">
  <page>...</page>                                   <!-- Letter, portrait -->
  <page orientation="landscape" margin="0.5in">...</page>  <!-- Letter, landscape -->
  <page page-size="A5">...</page>                    <!-- A5, portrait -->
</document>

Layout & pagination

Flow

The blocks inside a page (<text>, <img>, <table>, <spacer>) are stacked from top to bottom inside the page's content area: the page minus its margins, minus any header or footer. When the next piece doesn't fit, a new page starts with the same size, margins and settings. Each <page> element always starts a new physical page.

Absolute positioning

Give a <text>, <img> or <table> an x and/or y to place it at exact coordinates instead of in the flow. Coordinates are measured from the top-left corner of the page (not the margin). A missing x defaults to the left margin, and a missing y to the top of the content area. Absolute blocks are drawn on the page the flow has reached at that point, and they don't take up space in the flow.

<text x="120mm" y="20mm" width="70mm" align="right" size="22" bold="true">INVOICE</text>
<img ref="stamp" x="75%" y="80%" width="30mm"/>

For absolute text and tables, width sets the box width. It defaults to the space from x to the right margin.

Text & fonts

Whitespace in text is collapsed the way HTML does it, so you can indent and wrap your XML freely. Use <br/> for a hard line break. A word too long for a line is broken at character boundaries.

<text align="justify">
  Normal, <b>bold</b>, <i>italic</i>, <u>underlined</u>,
  <span color="#c00" size="14">red and larger</span>,<br/>
  and a new line.
</text>

Loose text written directly in a <page>, <cell>, <header> or <footer> becomes an implicit paragraph.

Fonts

xml2pdf uses the standard PDF fonts, which every PDF viewer has built in, so nothing needs embedding:

font valueFont
helvetica, arial, sans, sans-serifHelvetica (default)
times, times-roman, times new roman, serifTimes
courier, courier new, monospaceCourier
zapfdingbatsZapfDingbats

Helvetica, Times and Courier each come in regular, bold, italic and bold-italic variants. ZapfDingbats (a set of symbols and dingbats) has a single variant.

Character set. The standard fonts cover Windows-1252: Latin-1 (é, ü, ñ, ß, …) plus €, “curly quotes”, ‘apostrophes’, – and — dashes, • and ™. Characters outside it, such as CJK, Cyrillic or emoji, can't be shown yet. Custom TrueType fonts with full Unicode are planned.

Styles

Text styling is set with style attributes, which work on almost every element. They cascade: document → page → block → table row → cell → inline span. Each level inherits from its parent and overrides what it sets itself.

<document font="times" size="11" color="#222">     <!-- base for everything -->
  <page align="justify">                            <!-- all text on this page -->
    <text size="14">Inherits times and #222 but is 14pt and justified</text>
  </page>
</document>

Font, size, bold, italic, underline, color, alignment and line-height are inherited. margin-top, margin-bottom and bg apply only to the element they're set on.

Named styles

Declare reusable styles in <resources> and apply them with style="...". A style can extends another one. An element can use several styles separated by spaces, applied left to right, and attributes on the element itself always win.

<resources>
  <style id="sans" font="helvetica"/>
  <style id="h1" extends="sans" size="20" bold="true" color="#1f4e9c" margin-bottom="8"/>
  <style id="muted" color="#777" size="9"/>
</resources>
...
<text style="h1">Title</text>
<text style="h1 muted" size="12">A muted heading at 12pt</text>

Colors

Colors accept #rgb, #rrggbb, rgb(r, g, b) (0–255), or a name: black, white, red, green, blue, yellow, orange, purple, gray/grey, lightgray, darkgray, navy, teal, maroon.

Variables

Write ${name} anywhere in text or attribute values. Names are looked up in this order, and the first match wins:

  1. loop variables of the enclosing <for-each> loops (innermost first)
  2. --set name=value on the command line (Options.Vars in Go)
  3. JSON data given with --data (a file or stdin) or --json (inline), deep-merged in the order given (Options.Data in Go)
  4. <const> definitions in the document
<constants>
  <const name="company" value="Acme Corp"/>
  <const name="brand" value="#1f4e9c"/>
  <const name="copyright">© ${year} ${company}</const>  <!-- may reference other variables -->
</constants>
<text color="${brand}">${copyright}</text>

Dotted names reach into JSON objects and arrays: given {"customer": {"name": "Globex"}, "items": [{"price": "9.99"}]}, ${customer.name} is Globex and ${items.0.price} is 9.99. Numbers keep their original formatting (12.50 stays 12.50), true/false print as-is, and null prints as an empty string.

Write $${ to output a literal ${. An undefined variable is an error that names the variable and its line. A circular constant definition is also an error.

Loops

<for-each> repeats its content once for each element of an array. It works anywhere: around table rows, blocks, inline text, or whole pages.

<table widths="0.5in,4*,1*">
  <row header="true"><cell>#</cell><cell>Item</cell><cell align="right">Price</cell></row>
  <for-each items="${items}" var="item" number="n">
    <row>
      <cell>${n}</cell>
      <cell>${item.name}</cell>
      <cell align="right">${item.price}</cell>
    </row>
  </for-each>
</table>

var names the current element (default item). index and number optionally name its 0-based and 1-based position. An empty array produces nothing. Loops nest, and an inner loop can iterate over a field of the outer element:

<for-each items="orders" var="order">
  <text style="h2">Order ${order.id}</text>
  <for-each items="order.lines" var="line">
    <text>${line.qty} × ${line.sku}</text>
  </for-each>
</for-each>

Wrapping <page> in a loop produces one page per record, which is handy for letters, certificates or labels:

<for-each items="recipients" var="r">
  <page><text>Dear ${r.name}, ...</text></page>
</for-each>

Headers & footers

<header> and <footer> hold content that repeats on every page (or on selected pages). In their text, {page} becomes the current page number and {pages} the total page count. These tokens use single braces and no $.

<header pages="2-"><text align="right" size="9">Annual report 2026</text></header>
<footer><text align="center" size="9">Page {page} of {pages}</text></footer>

The header sits at the top margin and the footer at the bottom margin, and the body's content area shrinks to make room for them. Their height is measured from their content unless you set height. gap (default 6pt) is the space between the band and the body. Use header="false" or footer="false" on a <page> to hide them there, for example on a cover page; the space is then given back to the content.

Watermarks

A <watermark> draws text or an image centered on each page (or at x/y), rotated and semi-transparent. You can add as many as you like.

<watermark text="DRAFT" size="100" color="red" opacity="0.12" rotate="45"/>
<watermark image="logo" width="60%" opacity="0.06" layer="back" pages="2-"/>
<watermark text="CONFIDENTIAL" size="12" rotate="0" opacity="0.6" y="15pt"/>

By default watermarks are drawn in front of the content, which keeps them visible over table backgrounds. Use layer="back" to draw them beneath the content instead. To hide watermarks on a particular page, set watermark="false" on that <page>.

Page selectors

The pages attribute on <header>, <footer> and <watermark> chooses where they appear. It's a comma-separated list of:

SelectorPages
all (default)every page
first, lastthe first or last page
odd, evenodd or even page numbers
3page 3
2-5pages 2 to 5
2-page 2 onwards

Page numbers count physical pages in the output, including overflow pages. For example, pages="first,last" selects the first and last pages.

Errors

xml2pdf is strict, so typos surface immediately instead of silently producing a wrong PDF. Errors name the element and its line number:

xml2pdf: invoice.xml: line 12: <text>: unknown attribute "colr"
xml2pdf: invoice.xml: line 30: unknown style "heading"
xml2pdf: invoice.xml: line 41: undefined variable "customer.nmae"
xml2pdf: invoice.xml: line 7: <image>: image not found: images/logo.png
xml2pdf: invoice.xml: line 55: row spans 5 columns but table has 4

When there's an error, no output file is written.

Limitations