glu
Callbacks

Callbacks

glu provides a callback system for page-level events. Callbacks are Lua functions that run at specific points during PDF generation — for example, drawing decorations or adding page numbers when a page is shipped out.

Registering a callback

Use frontend.add_callback() to register a named callback for an event:

local frontend = require("glu.frontend")

frontend.add_callback("pre_shipout", "page_frame", function(doc, page, pagenum)
    -- draw a frame, add a page number, etc.
end)

Each callback has a name (here "page_frame") that identifies it. Names must be unique per event — registering a callback with the same name replaces the previous one.

Events

Event When it fires Arguments
pre_shipout Before a page is written to the PDF doc, page, pagenum

pre_shipout arguments

Argument Type Description
doc Document The current document (same as from frontend.new())
page Page The page about to be shipped out
pagenum number The page number (1-based)

The page object provides page.width and page.height as dimensions. Use page.width.pt to get the numeric value in points (for calculations or SVG generation).

Place content on the page with:

page:output_at(x, y, content)

Callback ordering

By default, callbacks run in the order they were registered. The optional fourth argument to add_callback controls positioning:

-- Append (default)
frontend.add_callback("pre_shipout", "background", fn)
frontend.add_callback("pre_shipout", "background", fn, "back")

-- Prepend
frontend.add_callback("pre_shipout", "watermark", fn, "front")

-- Relative positioning
frontend.add_callback("pre_shipout", "border", fn, { after = "background" })
frontend.add_callback("pre_shipout", "header", fn, { before = "footer" })
Position Description
"back" (default) Append to the end of the callback list
"front" Insert at the beginning
{after = "name"} Insert after the named callback
{before = "name"} Insert before the named callback

If the referenced callback name does not exist, before/after falls back to appending.

Removing and listing callbacks

Remove a callback by name:

frontend.remove_callback("pre_shipout", "page_frame")

List all registered callback names for an event (in execution order):

local names = frontend.list_callbacks("pre_shipout")
for _, name in ipairs(names) do
    print(name)
end

Where to register callbacks

In Markdown mode, callbacks are typically registered in the companion Lua file. For example, if your document is report.md, put callback registrations in report.lua:

report.md      ← your document
report.lua     ← callback registrations (loaded automatically)

In Lua script mode, register callbacks before creating pages.

Example: page frame with SVG

This example draws a decorative blue frame on every page:

local frontend = require("glu.frontend")

frontend.add_callback("pre_shipout", "page_frame", function(doc, page, pagenum)
    local w = page.width.pt
    local h = page.height.pt
    local m = 36  -- 0.5in margin for the frame

    local svg = string.format(
        '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 %g %g">' ..
        '<rect x="%g" y="%g" width="%g" height="%g" ' ..
        'fill="none" stroke="#336699" stroke-width="1.5" rx="4" ry="4"/>' ..
        '</svg>',
        w, h, m, m, w - 2*m, h - 2*m
    )

    local svgdoc = frontend.parse_svg_string(svg)
    local svgnode = doc:create_svg_node(svgdoc, page.width, page.height)
    page:output_at(0, page.height, svgnode)
end)

The callback uses inline SVG to draw the frame. frontend.parse_svg_string() parses the SVG, and doc:create_svg_node() converts it to a node that can be placed on the page.

Example: page numbers

local frontend = require("glu.frontend")

frontend.add_callback("pre_shipout", "page_number", function(doc, page, pagenum)
    local ff = doc:load_font_family("serif")
    local txt = frontend.text({
        font_family = ff,
        font_size = "9pt",
        color = "#666666",
    })
    txt:append(tostring(pagenum))

    local vl = doc:format_paragraph(txt, "50pt", { alignment = "center" })
    local x = (page.width - frontend.sp("50pt")) / 2
    page:output_at(x, frontend.sp("1.5cm"), vl)
end)