glu

glu — Markdown, HTML & Lua to PDF

glu converts Markdown and HTML files to PDF, or executes Lua scripts for full programmatic control over PDF output.

glu document.md    # → document.pdf
glu page.html      # → page.pdf
glu script.lua     # → script.pdf

Installation

Download the latest binaries at https://github.com/speedata/glu/releases/latest.

If you have a Go installation, you can also build from source:

git clone https://github.com/speedata/glu
cd glu
rake build

Quick start

Create a file hello.md:

---
title: Hello World
---

# Hello World

This is a **bold** and *italic* paragraph.

| Column A | Column B |
|----------|----------|
| Value 1  | Value 2  |

Run with:

glu hello.md

This produces hello.pdf — an A4 page with serif fonts, proper headings, and a formatted table. No configuration needed. You can also pass .html files for full control over structure and styling.

Dynamic content with Lua

glu supports embedded Lua for calculations, data processing, and dynamic content. If a file hello.lua exists alongside hello.md, it is loaded automatically:

hello.lua:

function greeting(name)
    return "Hello, " .. name .. "!"
end

hello.md:

# Welcome

{= greeting("World") =}

The {= expr =} syntax inserts the result of any Lua expression. See Markdown mode for the full feature set including Lua code blocks, YAML frontmatter, and custom CSS.

Callbacks

glu provides a callback system for page-level and element-level events. For example, drawing a frame on every page or decorating headings:

local frontend = require("glu.frontend")

-- Runs when a new page is created (for backgrounds)
frontend.add_callback("page_init", "bg", function(doc, page, pagenum, pageinfo)
    -- draw backgrounds, watermarks, etc.
end)

-- Runs before each page is written to the PDF (for overlays)
frontend.add_callback("pre_shipout", "frame", function(doc, page, pagenum, pageinfo)
    -- draw frames, add page numbers, etc.
end)

-- Runs after each block element (h1, p, ul, ...) is built
frontend.add_callback("post_element", "highlight", function(element, doc)
    -- collect headings, add backgrounds, etc.
end)

Named callbacks can be ordered and removed dynamically. See Callbacks for details.

Lua script mode

For full programmatic control, pass a .lua file. This hello world example creates a PDF with a formatted paragraph:

local frontend = require("glu.frontend")

-- Load a font
local doc = frontend.new("hello.pdf")
local ff = doc:new_font_family("text")
local fs = frontend.fontsource({ location = "fonts/CrimsonPro-Regular.ttf" })
ff:add_member(fs, "regular", "normal")

-- Create text content
local txt = frontend.text({
    font_family = ff,
    font_size = "12pt",
    color = "black"
})

local str = [[The quick brown fox jumps over the lazy dog with a very
long line that should be wrapped at some point. This is a test to see
how the text is formatted when it is too long to fit on one line.]]

-- Remove newlines
str = str:gsub("\n", " ")
txt:append(str)

-- Format paragraph and create page
local vlist = doc:format_paragraph(txt, "225pt", { leading = "14pt" })

local page = doc:new_page()
page.width = "210mm"
page.height = "297mm"
page:output_at("1cm", "28cm", vlist)
page:shipout()

doc:finish()
print("Created hello.pdf")

See Lua script mode for a step-by-step explanation and all available modules.

Documentation