Examples#
Each example below is a complete, runnable report from the
Examples/ directory.
Every preview on this page is the actual rendered PDF — produced by running:
ym convert report.yml
Grab any example folder, tweak the YAML, and re-render to see your changes.
Simple example#
The essentials: headings from keys, word-wrapped paragraphs, a sub-heading, nested bullets, an ordered list, and a table. Start here, then read Document structure.
Report title:
- >
Here is the first paragraph. You will notice that I have used the `-` character to create
a YAML list. Under the first list item, I have also used the `>` character which tells
the parser that you plan on entering a multi-line string that is to be word-wrapped.
You can insert linebreaks wherever you want within the YAML
source but new paragraphs won't be created unless you put use two new line characters.
- Sub-heading: >
In order to create a report structure of Heading -> Paragraph -> Subheading, we have to
use lists. This is because a YAML key can only have one value. A Paragraph and a Subheading
coming under the top level heading is effectively two values for the YAML Heading key.
However, if you want to have a structure of Heading -> Paragraph, Heading -> Paragraph, then
you can use a simple, key: value structure.
- Standard content types:
- Bullets:
_ul:
- Bullet 1
- Bullet 2
- Bullet 3
- - Bullet 4
- Bullet 5
- Bullet 6
- Ordered list:
_ol:
- First item
- Second item
- - Nested first
- Nested second
- Nested third
- Tables:
- Item Number: 12.01
Description: There is a problem here. This report documents it.
Location: Under the stairs
- Item Number: 12.02
Description: There are five ladybugs on the table cloth
Location: Kitchen
- Item Number: 45
Description: The item numbers are arbitray values you structure.
Location: This table
Page 1
Page 2
Document configuration#
Override the body font, heading font, heading colour, and typographic ratio with a _style
block in the document front matter. See Configuration.
_style:
body:
font: NotoSans
headings:
font: AppleGaramond
ratio: minor third
color: "#dd9922"
Customizing your document configuration and styling:
- >
There are `three` categories of document configuration
- _ol:
- The document template
- Text styles
- Table styles
- >
These three categories are set at three different levels of priority
- _ol:
- The internal defaults (lowest priority)
- Project config
- Document internal config (highest priority)
- >
Each level of priority in the configuration heirarchy effectively inherits the configuration of the level of priority above it.
Using a combination of configuration levels, you can customize the style of your documents with a minimum amount of configuration text.
- The document template (_doc):
- >
The `_doc` configuration controls the document-wide page size and orientation, along with
one or more named `page templates`. Each page template controls settings such as
- - Margins
- PDF background file path
- >
The first page template listed is the one your document starts on. Within your report you
can switch between page templates using the `_pagebreak` block (pass it the name or index of
the template to use for the following pages) or the `_nextpagetemplate` block (which arms the
template to switch to at the next page break).
- >
Currently, the document template is internally configured to only use a single content frame. This limitation would only affect you if you wanted to do something like a two-column layout (where text flows into the second column on the same page once the first one is full).
- Text styles (_style):
- >
With text styles, you can control the body text
- Text styling attributes:
_ul:
- Font family
- Text color
- Size
- Line spacing ratio
- "Bullet style:"
- - Bullet color
- Bullet symbol heirarchy
- Bullet indentation
- >
You can also control the text heading styles separately from the body text by adjusting their
- _ul:
- Font family
- Text color
- Typographic ratio (in musical intervals, e.g. "minor second" or "major third")
- Alignment (left, center, right, justify) and underline
- >
Beyond the default `body` and `headings`, you can define named `styles` — alternate text
families you switch between within the document using the `_textstyle` block. Each named
style is a sparse override that inherits everything you don't change from the defaults.
- Table styles (_tablestyle):
- >
You can control how your tables are displayed by modifying the table style. With the _tablestyle key, you can control
- _ul:
- "Table header:"
- - background (color)
- gridlines (above, below, between)
- "text: font, size, color"
- "Table rows:"
- - even (rows, background color)
- odd (rows, background color)
- gridlines (above, below, between)
- "text: font, size, color"
Page 1
Page 2
Scoped text styles#
Define named text styles under _style.styles, then switch the active family part-way through
the document with the _textstyle block. Here a fine-print style (smaller, grey) and a
callout style (larger, blue, centred) are switched in and out — each swap restyles the body,
headings, and lists that follow it, and reverts when the section ends. See
Configuration → Named text styles.
_style:
body:
font: Helvetica
size: 11
align: justify # left | center | right | justify
headings:
font: Helvetica
ratio: major third
color: "#dd9922"
underline: true # underline all headings
styles: # named styles, each inherits from body/headings
fine-print:
body:
size: 7
color: "#888888"
callout:
body:
size: 14
color: "#2266cc"
align: center # this style centers its paragraphs
Scoped text styles:
- >
This opening paragraph uses the default body style. Named text styles let you switch
the active family for part of a document.
- Fine print section:
- _textstyle: fine-print
- >
Everything from here down in this section renders in the fine-print style — smaller
and grey. Because switching swaps the whole family, the heading above is scaled to
the fine-print size too.
- A nested sub-clause:
- This inherits fine-print (a child of the switched section).
- _textstyle: default
- >
And now we have switched back to the default style for the remainder of this section.
- A callout:
- _textstyle: callout
- >
This paragraph uses the callout style — larger and blue.
- Back to normal:
- >
This section is default again; the callout scope ended with its list.
Document variables#
Define _vars, render them into text with Jinja ({{ "{{var}}" }}), and reach into nested
values. See Variables.
_vars:
a: "hello"
b: true
c: 23
d: -0.013
e: {'key': 'value', 'key2': 'val2'}
f: ['val1', 'val2']
Document Variables (_vars):
- >
When creating a report yaml document, you have the option to include document-level variables by using the
'_vars' key in the root of the document.
These variables can be rendered as part of your strings (using jinja) within the report content using the double-braces
(e.g. {% raw %}{{variable_name}}{% endraw%}). When rendered using jinja, all values are stringified and only the string
representation is shown (i.e. the result of str(var)).
This document has five variables defined, a, b, c, d, e.
- _ul:
- a = {{a}}
- b = {{b}}
- c = {{c}}
- d = {{d}}
- e = {{e.key2}}
- f = {{f}}
- Passing Python Objects: >
It is also possible to pass variable data as Python objects to blocks (see "YMPrint blocks") using the $var syntax (similar to bash).
You cannot use the dollar-sign variable name syntax (e.g. $varname) within content that is to be displayed (if attempted, an
error will be raised). The intended use-case is for passing data within the document to be used as <i>block parameters</i>.
For example, you might want to have some consistent spacer used throughout your document. Instead of typing "_spacer: 5" at all
locations, you can instead set a variable in the _vars to something like "small_space: 5" and employ the variable at each spacer
as such "_spacer: $small_space".
The data you set in the _vars key can be any of the native YAML datatypes: str, int, float, null/None, bool, list, and dict.
- Computing new Python objects within code: >
It is also possible to use a _py code block (see "YMPrint blocks") to create variables of arbitrary classes/datatypes and pass
that data to block parameters.
A ready example of this would be the _matplotfig block which has a "fig" parameter that expects a matplotlib figure.
You would first generate the figure in a _py block and assign the generated figure to a variable. You would then use the
dollar-sign syntax to pass that computed variable into the "fig" parameter of the _matplotfig code block (see YMPrint blocks
for details.)
Page 1
Page 2
PDF backgrounds#
Overlay the document onto a designed PDF template and auto-populate its form fields from
_vars (here: field_a, field_b, field_c, field_d). See
PDF backgrounds.
_doc:
templates:
default:
margins:
top: 72.0
left: 72.0
right: 72.0
bottom: 72.0
background:
filepath: background.pdf
relative-to: source
_vars:
field_a: 1645
field_b: 99 Sycamore St, Canada
field_c: $4434.04
field_d: An enormous moose
_style:
body:
size: 9
headings:
ratio: major second
Custom styling with PDF backgrounds:
- >
You can design your own custom templates in an external PDF document and use it as a background for your YMPrint document
(as you can see in this document). The fields are populated and the form fields are flattened so that the populated values
look like normal PDF content.
The PDF content from the report is overlayed on top of the existing document content in the background.
- Auto-populated PDF form fields from variables: >
One of the original design intents of inline document variables is to use them to dynamically populate PDF
form fields. By doing so, you can create documents with dynamic headers that give the illusion of working with
a more sophisticated page layout software.
Form fields are _automatically_ populated when a document variable name exactly matches the PDF form field name.
If you want to avoid this behaviour:
_ul:
- Make sure that none of your variable names match your field names (i.e. using a special naming convention for your form field names to prevent accidental clashes)
- Do not use form fields
Example: The form fields below have been populated from the document vars
YMPrint blocks#
The full block catalogue in one document: images, admonitions, block quotes, page breaks,
horizontal rules, spacers, executable _py, a non-executable _code block, and _loadjson.
See the Blocks reference.
_doc:
body:
font: NotoSans
headings:
font: NotoSans
Using Blocks:
- >
YMPrint uses a special "block syntax" for defining custom content types. Some of these are similar to
content types you might expect to see in markdown, e.g. images or code blocks, or extensions to common
markdown such as admonitions.
- Images (`_img`):
_img:
source: catpuccin.png # Either relative or absolute path. Relative paths are relative to the .yml report file.
caption: "Figure 1: The catpuccin cat"
width_ratio: 0.3
- Admonitions:
- "The following admonitions blocks are available:"
- _ul:
- _info
- _warning
- _danger
- _tip
- _note
- They are rendered below
_info: Here is an "info" admonition
_warning: Here is a "warning" admonition
_danger: Here is a danger admonition
_tip: Here is a helpful tip
_note: This is useful when you want to give a note
- Block quotes (`_blockquote`):
_blockquote:
quote: “What you do makes a difference, and you have to decide what kind of difference you want to make.”
attribution: Jane Goodall
- Page breaks (`_pagebreak`):
- >
You can insert an arbitrary page break using _pagebreak, such as the one below. _pagebreak accepts null as a value.
- _pagebreak:
- Horizontal rules (`_hrule`):
- >
Unlike a horizontal rule in Markdown (which does not have properties you can adjust)
the `_hrule` block in YMPrint has optional parameters like width_ratio,
thickness, cap, and color that you can manipulate.
- _hrule_default: null
- _hrule_red:
width_ratio: 0.8
thickness: 2
color: "#bb3322"
- _hrule_blue:
width_ratio: 0.6
thickness: 3
color: "#4422dd"
cap: round
- _hrule_green:
width_ratio: 0.4
thickness: 4
color: "#22dd44"
- _hrule_yellow:
width_ratio: 0.2
thickness: 6
color: "#dddd22"
cap: round
- Vertical spacers (`_spacer`):
- >
You can create arbitrary amounts of vertical white space so you can manually
adjust the positioning of your content.
The `_spacer` object accepts a distance in points for the amount of vertical
space to insert.
Below, a spacer is inserted in between each line of text
- _spacer: 5
- There is a 5 pt spacer above
- _spacer: 10
- There is a 10 pt spacer above
- _spacer: 20
- There is a 20 pt spacer above
- _spacer: 40
- There is a 40 pt spacer above
- Executable code Blocks (`_py`):
- >
In a `_py` code block, Python code will be executed using `exec()` with the "vars"
dictionary from the document context passed in as its global scope and an optional namespace
dictionary as its local scope. If no namespace is passed, the "vars" dictionary will be the local
scope.
Any "_vars" you set in your document
front matter will be available to your Python code and any variables you create in your
Python code will be available to your document vars.
The Python environment used to execute is the same environment that YMPrint is running insert.
At this time, external Python subprocesses have not been implemented.
- _py:
echo: true
caption: >
Once this code executes, the variables will be accessible from the "vars" key in the document context under
a namespace called py1.
line_numbers: true
namespace: py1
source: |
import math
a = 3
b = 4
c = math.sin(a/b)
acc = []
for k in [a, b, c]:
acc.append(k)
- "Now, the values of the variables a, b, and c can be included in the document:"
- _ul:
- a = {{py1.a}} (using py1.a)
- b = {{py1.b}} (using py1.b)
- c = {{py1.c}} (using py1.c)
- _code:
source: |
yaml_data: is being shown
you can put: any yaml data together
more examples:
- - cell-padding
- cell_size
- row_size
- Dynamic JSON variables (`_loadjson`):
- >
You can dynamically build your vars context by including a _loadjson block in your
document.
Similar to the _py block, you can pass an optional namespace parameter to load the
variables into a namespace under the vars dictionary. If no namespace is passed,
then the variables will be available under the vars dictionary.
- _loadjson:
path: extra_vars.json
namespace: extra_vars
- "Here are the values of the vars contained within the 'extravars' namespace:"
- _ul:
- bn = {{extra_vars.bn}}
- dx = {{extra_vars.dx}}
Page 1 — images & admonitions
Page 2 — quotes, rules & spacers
Page 3 — executable Python
Page 4 — code & JSON variables
Slides#
A 1080p deck (page-size: [1920, 1080]) in slide mode: custom layouts and frames, a
different PDF background for title, section, content and closing slides, and most of the
block catalogue (code, executed Python, matplotlib, admonitions, tables) sized for a
screen. The four backgrounds are vector PDFs drawn with ReportLab by
make_backgrounds.py, which sits next to the deck. See
Slide presentations.
# A 1080p (1920 x 1080 pt) slide deck that shows off YMPrint's blocks inside slides.
#
# The vector backgrounds in backgrounds/ are generated by make_backgrounds.py:
# uv run python make_backgrounds.py
# Render the deck with:
# ym convert slides.yml
_vars:
title: YMPrint Slides
author: Connor Ferster · Structural Python
year: 2000
_doc:
page-size: [1920, 1080] # 1 pt = 1 px on a 1080p display
slides: true
templates:
hero: # listed first: the deck's first slide always uses it
margins: &margins {top: 90, left: 120, right: 120, bottom: 110}
background: {filepath: backgrounds/hero.pdf, relative-to: source}
content:
margins: *margins
background: {filepath: backgrounds/content.pdf, relative-to: source}
section:
margins: *margins
background: {filepath: backgrounds/section.pdf, relative-to: source}
closing:
margins: *margins
background: {filepath: backgrounds/closing.pdf, relative-to: source}
# Frames are [x, y, width, height] inside the 1680 x 880 content box.
# Values <= 1 are fractions of the box, values > 1 are points.
layouts:
default:
title: {box: [0, 0, 1, 150], valign: bottom}
body: [0, 190, 1, 690]
two-column:
title: {box: [0, 0, 1, 150], valign: bottom}
left: [0, 190, 0.47, 690]
right: [0.53, 190, 0.47, 690]
code-right:
title: {box: [0, 0, 1, 150], valign: bottom}
text: [0, 190, 0.36, 690]
code: [0.40, 190, 0.60, 690]
figure-right:
title: {box: [0, 0, 1, 150], valign: bottom}
text: [0, 190, 0.34, 690]
figure: {box: [0.37, 165, 0.63, 715], valign: middle}
hero:
title: {box: [0, 0.10, 0.58, 0.48], valign: bottom}
body: [0, 0.62, 0.58, 0.26]
closing:
title: {box: [0.1, 0.02, 0.8, 0.30], valign: bottom}
body: [0.1, 0.34, 0.8, 0.14]
_style:
headings:
font: Montserrat
color: "#1b1446"
ratio: minor third # h1 = 38 × 1.2⁵ ≈ 95 pt
body:
font: Inter
size: 38
color: "#2e2856"
spacing: 1.4
bullets:
font: DejaVuSans
size: 30
color: "#e0218a"
symbols: "▸▹•◦"
spacing: 14
indent-bullet: 4
indent-text: 52
styles:
hero: # light text for the dark synthwave slides
body:
size: 40
color: "#d9d4ff"
bullets: {color: "#22e4ff"}
headings:
color: "#ffffff"
ratio: major third
closing:
body:
size: 40
color: "#d9d4ff"
align: center
headings:
color: "#ffffff"
ratio: major third
align: center
small:
body:
size: 32
bullets: {size: 26}
callout:
body:
font: Montserrat
size: 46
color: "#e0218a"
align: center
_tablestyle:
cell-padding: {top: 14, left: 28, right: 28, bottom: 14}
headers:
text: {font: Montserrat, size: 30, color: "#ffffff", bold: true}
row: {color: "#1b1446", lines: []}
body:
text: {font: Inter, size: 30, color: "#1b1446"}
rows:
color: {even: "#ffffff", odd: "#eceafa"}
lines: []
# ---------------------------------------------------------------------------
"**YMPrint** <font color='#22e4ff'>Slides</font>":
- _slide: {layout: hero, template: hero}
- _textstyle: hero
- Desktop publishing with YAML, now in 16:9.
- "<font size='26' color='#ff8ad8'>{{author}}</font>"
A deck is just a YAML file:
- _slide: {layout: code-right, template: content}
- _textstyle: small
- _ul:
- Every top-level heading is **one slide**
- Frames place content side by side
- Backgrounds are ordinary PDFs, drawn in anything
- Renders to a crisp **vector** PDF
- _frame: code
- _code:
width_ratio: 1
caption: slides.yml — this very slide
source: |
A deck is just a YAML file:
- _slide:
layout: code-right
template: content
- _textstyle: small
- _ul:
- Every top-level heading is **one slide**
- Frames place content side by side
- Backgrounds are ordinary PDFs, ...
- Renders to a crisp **vector** PDF
- _frame: code
- _code:
caption: slides.yml — this very slide
source: ...
"<font color='#22e4ff'>01</font> Layouts & frames":
- _slide: {layout: hero, template: section}
- _textstyle: hero
- Put anything anywhere, as long as it is a rectangle.
Layouts are named rectangles:
- _slide: {layout: two-column, template: content}
- _textstyle: small
- Define a layout once under `_doc`, then pick it per slide with `_slide`.
- Each frame is `[x, y, width, height]`, measured from the top-left of the content box.
- Send content to a frame with `_frame`, and it sizes itself to fit.
- _frame: right
- _code:
width_ratio: 1
caption: _doc → layouts
source: |
layouts:
two-column:
title: {box: [0, 0, 1, 150],
valign: bottom}
left: [0, 190, 0.47, 690]
right: [0.53, 190, 0.47, 690]
Images drop into any frame:
- _slide: {layout: figure-right, template: content}
- _textstyle: small
- "Our logo, faithfully preserved at full **year {{year}}** fidelity."
- _spacer: 20
- "An `_img` with `width_ratio: 1` fills its frame. A frame with `valign: middle` centres it."
- _frame: figure
- _img:
source: ../../ymprintlogolarge.png
width_ratio: 1.6 # small images are scaled up
caption: "Figure 1: Desktop publishing, circa 2000"
"<font color='#22e4ff'>02</font> Code that runs":
- _slide: {layout: hero, template: section}
- _textstyle: hero
- Python executes while the PDF renders. The results land on the slide.
"Run it, then show it: `_py`":
- _slide: {layout: two-column, template: content}
- _py:
namespace: beam
width_ratio: 1
line_numbers: true
caption: Executed during ym convert
source: |
w = 12.0 # kN/m, uniform load
L = 8.0 # m, span
E = 200e6 # kN/m², steel
I = 3.0e-4 # m⁴
M_max = w * L**2 / 8
V_max = w * L / 2
delta = 5*w*L**4 / (384*E*I) * 1000
limit = L * 1000 / 360
ok = delta < limit
- _frame: right
- _textstyle: small
- "The variables flow straight into a table with `{{ '{{ beam.M_max }}' }}`:"
- _spacer: 10
- - Quantity: Max. moment
Value: "{{ '%.1f' | format(beam.M_max) }} kN·m"
- Quantity: Max. shear
Value: "{{ '%.1f' | format(beam.V_max) }} kN"
- Quantity: Deflection
Value: "{{ '%.1f' | format(beam.delta) }} mm"
- Quantity: Limit (L/360)
Value: "{{ '%.1f' | format(beam.limit) }} mm"
- Quantity: Check
Value: "{{ 'PASS' if beam.ok else 'FAIL' }}"
"Plots from live objects: `_matplotfig`":
- _slide: {layout: figure-right, template: content}
- _py:
echo: false
source: |
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
x = np.linspace(0, beam["L"], 200)
M = beam["w"] * x * (beam["L"] - x) / 2
V = beam["w"] * (beam["L"] / 2 - x)
ink, magenta, cyan = "#1b1446", "#e0218a", "#0fb5d4"
plt.rcParams.update({"font.size": 15, "text.color": ink, "axes.labelcolor": ink,
"xtick.color": ink, "ytick.color": ink, "axes.edgecolor": ink})
bmd_fig, (ax_m, ax_v) = plt.subplots(2, 1, figsize=(10, 6.8), sharex=True)
ax_m.fill_between(x, M, color=magenta, alpha=0.18)
ax_m.plot(x, M, color=magenta, lw=3)
ax_m.invert_yaxis()
ax_m.set_ylabel("Moment (kN·m)")
ax_v.fill_between(x, V, color=cyan, alpha=0.18)
ax_v.plot(x, V, color=cyan, lw=3)
ax_v.set_ylabel("Shear (kN)")
ax_v.set_xlabel("Position along span (m)")
for ax in (ax_m, ax_v):
ax.axhline(0, color=ink, lw=1)
ax.spines[["top", "right"]].set_visible(False)
ax.grid(alpha=0.25)
bmd_fig.tight_layout()
- _textstyle: small
- A hidden `_py` block builds a matplotlib figure from the beam on the last slide.
- _spacer: 16
- "`$bmd_fig` hands the live object to the block:"
- _code:
width_ratio: 1
source: |
_matplotfig:
fig: $bmd_fig
scale_ratio: 1
- _frame: figure
- _matplotfig:
fig: $bmd_fig
scale_ratio: 1
"<font color='#22e4ff'>03</font> Words, lists & tables":
- _slide: {layout: hero, template: section}
- _textstyle: hero
- Everything a document can do, a slide can do.
Inline markup & variables:
- _slide: {layout: code-right, template: content}
- _textstyle: small
- Write **bold**, *italic* and `inline code` in plain YAML strings.
- "Variables interpolate anywhere: this deck honours the year {{year}}."
- "ReportLab markup passes through, for <font color='#e0218a'>colour</font> and <font size='44'>size</font>."
- _spacer: 30
- _p:
style: callout
content: "_p picks a named style: this is “callout”."
- _frame: code
- _code:
width_ratio: 1
caption: The source of the text on the left
source: |
- Write **bold**, *italic* and `inline code`
in plain YAML strings.
- "Variables interpolate anywhere: this deck
honours the year {{year}}."
- "ReportLab markup passes through, for
<font color='#e0218a'>colour</font> and
<font size='44'>size</font>."
- _p:
style: callout
content: "_p picks a named style: ..."
Callouts scale with your text:
- _slide: {layout: two-column, template: content}
- _tip: Admonitions, quotes and captions size themselves from the body text, so they read on a 1080p slide.
- _warning: This deck has a 38 pt body, so these callouts are drawn 3.8 times their size in a 10 pt report.
- _frame: right
- _note: Five kinds are available — _info, _warning, _danger, _tip and _note.
- _blockquote:
quote: “What you do makes a difference, and you have to decide what kind of difference you want to make.”
attribution: Jane Goodall
Lists, ordered and not:
- _slide: {layout: two-column, template: content}
- _ul:
- Gathering information
- - Site visits
- Drawings and specs
- Simulation
- - Hand calcs
- Finite element models
- Reporting
- _frame: right
- _ol:
- Write YAML
- Run `ym convert`
- Present
- - Full screen
- Any PDF viewer
Tables are lists of mappings:
- _slide: {layout: code-right, template: content}
- _textstyle: small
- Every mapping is a row, and its keys are the column headers.
- _spacer: 20
- "Header and row colours come from `_tablestyle`."
- _frame: code
- - Year: "1984"
Technology: PostScript
Role in this slide: The page description idea
- Year: "1993"
Technology: PDF 1.0
Role in this slide: The file you are looking at
- Year: "2000"
Technology: ReportLab & Python 2.0
Role in this slide: The renderer and its language
- Year: "2001"
Technology: YAML
Role in this slide: The source format
- Year: "2026"
Technology: YMPrint slides
Role in this slide: The glue
"Thank <font color='#ff2bd6'>you</font>":
- _slide: {layout: closing, template: closing}
- _textstyle: closing
- "<font face='DejaVuSansMono' color='#22e4ff'>$ ym convert slides.yml</font>"
- The technology of the year {{year}}… today!
Slide — Title slide — custom hero background
Slide — A slide that shows its own source
Slide — Images in a frame
Slide — Executed Python feeding a table
Slide — A live matplotlib figure
Slide — Admonitions and quotes, scaled for slides
Slide — Tables from lists of mappings
Slide — Closing slide