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.pdfInstallation
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 buildQuick 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.mdThis 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 .. "!"
endhello.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
- Markdown mode — Markdown to PDF conversion (frontmatter, Lua blocks, CSS)
- HTML mode — HTML to PDF conversion (full CSS,
<style>tags) - Callbacks — lifecycle and page/element event callbacks
- Lua script mode — programmatic PDF creation with full Lua API
- Using glu — command line options and general usage
- glu.frontend module — Lua typesetting API (documents, text, fonts, tables)
- glu.json module — JSON encoding, decoding, file I/O
- glu.log module — structured logging (debug, info, warn, error)
- glu.pdf module — low-level PDF writing
- xml.cxpath module — XPath XML querying
- Backend modules — glu, glu.node, glu.font
- glu.textshape module — low-level text shaping (HarfBuzz port)