glu
Markdown mode

Markdown mode

glu can convert Markdown files directly to PDF. Instead of writing Lua scripts, you write Markdown and glu handles the typesetting automatically.

glu document.md

This produces document.pdf. glu detects the mode by the file extension: .lua for Lua scripts, .md for Markdown.

Pipeline

The Markdown-to-PDF pipeline has several stages:

.md file → frontmatter → companion .lua → read aux → Lua blocks → inline expressions → goldmark → HTML → htmlbag → PDF → write aux
                          ↑ callbacks      ↑ _aux/_toc   ↑ return values    ↑ {= expr =}                                    ↑ _aux
                            registered       available    ↑ startrecording/                                                    saved
                                                            stoprecording

Each stage is optional — a plain Markdown file without any Lua or frontmatter works fine.

YAML frontmatter

A YAML block at the beginning of the file sets document metadata:

---
title: My Document
author: Jane Doe
css: custom.css
papersize: a4
---
Field Description
title PDF document title
author PDF author field
css Additional CSS file to load
papersize Page size (a4, letter, 20cm 20cm, etc.)

Companion Lua file

If a Lua file with the same base name exists (e.g., report.lua for report.md), it is loaded automatically before the Markdown is processed. This is the place for function definitions, variable initialization, and callback registration.

report.md      ← your document
report.lua     ← loaded automatically (if present)

Example report.lua:

local frontend = require("glu.frontend")

function currency(amount)
    return string.format("%.2f €", amount)
end

total = 42 * 3

The functions and variables are available in the Markdown file via Lua blocks and inline expressions.

Lua blocks

Fenced code blocks with {lua} are executed and removed from the output:

```{lua}
result = math.sqrt(144)
```

Lua blocks run in the same Lua state as the companion file. They execute in document order, so later blocks can use variables from earlier ones.

Returning text

If a Lua block uses return, the returned string is inserted into the Markdown source at the position of the block:

```{lua}
return '<div class="note">'
```

This paragraph is wrapped in a div.

```{lua}
return '</div>'
```

This is useful for injecting HTML tags, dynamic content, or computed Markdown.

Recording and processing text

Two Lua blocks can work together to capture and transform a section of text. startrecording() begins capturing all text between Lua blocks; stoprecording() stops capturing and returns the captured text as a Lua string:

```{lua}
startrecording()
```

This text will be captured and processed.

```{lua}
local content = stoprecording()
return string.upper(content)
```

The result: “THIS TEXT WILL BE CAPTURED AND PROCESSED.” — the Markdown between the two blocks was captured, transformed by string.upper(), and re-inserted via return.

Recording works on the raw Markdown text level (before goldmark converts to HTML). This means:

  • Markdown formatting (**bold**, # heading) inside the recorded text is preserved and processed normally after the Lua transformation
  • {= expr =} inline expressions inside recorded text are expanded after the Lua transformation — if the transformation alters the {= ... =} syntax, the expressions will break
Function Description
startrecording() Begins capturing text. Subsequent text goes to the buffer instead of the output.
stoprecording() Stops capturing and returns the captured text as a string.

Inline expressions

Use {= expr =} to insert the result of a Lua expression:

The total is {= total =} and the square root of 144 is {= math.sqrt(144) =}.

Any Lua expression that returns a value works — variables, function calls, arithmetic:

The price is {= currency(19.99) =}.

Markdown features

glu uses goldmark with these extensions:

  • Tables — pipe tables with alignment
  • Strikethrough~~deleted~~
  • Autolinks — URLs are automatically linked

Standard Markdown features: headings, bold, italic, code, lists, blockquotes, horizontal rules, and links.

Raw HTML is supported inside Markdown. This allows using CSS features like leaders for table-of-contents dot fills via inline <span> elements.

Default stylesheet

Markdown mode includes a built-in CSS stylesheet that provides sensible defaults:

  • A4 page size with 2cm margins
  • Serif font (CrimsonPro) for body text at 10pt
  • Scaled heading sizes (h1: 24pt down to h6: 10pt italic)
  • Monospace font (CamingoCode) for code and pre blocks
  • Table styling with header borders
  • Blockquote indentation and italic style

The defaults can be overridden with a custom CSS file.

Custom CSS

Additional CSS can be loaded in two ways:

Via frontmatter:

---
css: style.css
---

Via command line:

glu --css style.css document.md

The custom CSS is applied after the default stylesheet, so it overrides matching rules. You can use @page to change the page size and margins:

@page {
    size: letter;
    margin: 1in;
}

body {
    font-size: 11pt;
    line-height: 1.5;
}

h1 {
    color: #333;
}

Page margin boxes and counters

CSS @page rules support margin boxes for headers and footers. Inside margin boxes, use the content property with counter() to display page numbers:

@page {
    size: a5;

    @bottom-center {
        content: "Page " counter(page) " of " counter(pages);
        font-size: 9pt;
        color: #666;
    }
}

Available counters:

Counter Description
page Current page number (1-based)
pages Total number of pages

The content property can mix literal strings and counter values freely: content: counter(page) " / " counter(pages);

Available margin boxes: top-left-corner, top-left, top-center, top-right, top-right-corner, bottom-left-corner, bottom-left, bottom-center, bottom-right, bottom-right-corner.

Auxiliary file

The counter(pages) counter requires the total page count, which is only known after the document has been fully typeset. glu solves this with an auxiliary file:

For a source file document.md, glu writes document-aux.json — a formatted JSON file stored next to the source.

  1. First run — glu typesets the document. counter(pages) shows 0 because no previous data exists. After the run, glu writes the actual page count to document-aux.json.
  2. Second run — glu reads document-aux.json and displays the correct values. If the data hasn’t changed, no further runs are needed.

If the aux data changed, glu logs a message:

·   Aux data changed — rerun to update cross-references

Use --clean to delete the aux file and start fresh:

glu --clean document.md

JSON format

The aux file uses _-prefixed keys for system data. User data can use any key without underscore prefix:

{
  "_headings": [
    { "level": "h1", "text": "Introduction", "page": 1 },
    { "level": "h2", "text": "Summary", "page": 3 }
  ],
  "_pages": 3,
  "my_index": ["Alpha", "Beta"]
}
Key Type Description
_pages number Total page count (set by glu after each run)
_headings array Heading entries with level, text, and page
user keys any Custom data stored by Lua code

Accessing aux data from Lua

The aux file is exposed as the _aux global table in Lua. It is readable and writable — any changes are saved back to the JSON file after the run.

-- Read system data from previous run
if _aux._pages then
    print("Last run: " .. _aux._pages .. " pages")
end

-- Read headings (also available as _toc for backward compatibility)
if _toc then
    for _, h in ipairs(_toc) do
        print(h.level .. ": " .. h.text .. " (page " .. h.page .. ")")
    end
end

-- Store custom data that persists across runs
_aux.build_count = (_aux.build_count or 0) + 1
_aux.my_keywords = { "typesetting", "PDF", "Lua" }

The _toc global is an alias for _aux._headings and remains available for backward compatibility.

System keys (with _ prefix) are updated by glu after each run. You can read and even overwrite them, but glu will set _pages and _headings to the actual values at the end of the run. To store persistent custom data, use keys without underscore prefix.

The lifecycle callbacks document_start, content_ready, and document_end provide precise control over when _aux is read or modified. Modifications made in document_end are included when the aux file is written.

Go templates

The --template flag enables Go template expansion before Markdown processing:

glu --template document.md

This processes the file through Go’s text/template engine first, allowing template directives like {{.Variable}} and {{range}} loops.

Debug modes

Two debug flags let you inspect intermediate stages of the pipeline:

--markdown — prints the expanded Markdown (after Lua blocks and inline expressions, before goldmark) to stdout:

glu --markdown document.md

--html — prints the generated HTML (after goldmark conversion, before PDF rendering) to stdout:

glu --html document.md

Both flags suppress PDF generation and write to stdout instead. Use them together with shell redirection to save the output:

glu --html document.md > output.html

Complete example

report.md:

---
title: Monthly Report
author: Jane Doe
---

# Monthly Report

This report covers the period from January to March.

| Month   | Revenue  | Expenses |
|---------|----------|----------|
| January | {= currency(15420) =} | {= currency(12300) =} |
| February| {= currency(18900) =} | {= currency(14100) =} |
| March   | {= currency(21050) =} | {= currency(15800) =} |

**Total revenue:** {= currency(15420 + 18900 + 21050) =}

---

> Report generated with glu.

report.lua:

function currency(amount)
    return string.format("%.2f €", amount)
end

Run:

glu report.md    # → report.pdf

Command line reference

glu [options] <filename.lua|filename.md|filename.html>

Options:
  --loglevel LVL    Set the log level (debug, info, warn, error)
  -q, --quiet       Suppress output on console
  --template        Apply Go template expansion (Markdown mode)
  --css FILE        Additional CSS file
  --markdown        Print expanded Markdown to stdout (debug)
  --html            Print generated HTML to stdout (debug, Markdown mode)
  --clean           Remove auxiliary files before processing