backend modules

Backend Modules

The backend modules provide low-level access to the boxes and glue typesetting engine. They expose the fundamental building blocks: scaled points, nodes, and font shaping.

For most use cases, the glu.frontend module is recommended. The backend modules are useful when you need fine-grained control over typesetting.

Modules Overview

Module Description
glu Scaled points, unit conversion, logging
glu.node Node types and list operations
glu.font Font instances and text shaping
local glu = require("glu")
local node = require("glu.node")
local font = require("glu.font")

glu Module

The glu module provides scaled point operations and logging functions.

Scaled Points

A scaled point (sp) is the internal unit used by boxes and glue. There are 65535 scaled points per DTP point.

The glu.sp() function returns a ScaledPoint userdata object, the same type used by the glu.frontend module. This provides type safety and arithmetic operations.

local glu = require("glu")

-- Convert string with unit to ScaledPoint
local sp = glu.sp("12pt")      -- ScaledPoint: "12pt"
local sp2 = glu.sp("1cm")      -- ScaledPoint: "28.35pt"
local sp3 = glu.sp("1in")      -- ScaledPoint: "72pt"
local sp4 = glu.sp("10mm")     -- ScaledPoint: "28.35pt"

-- Create from points
local sp = glu.sp_from_pt(12)  -- ScaledPoint from 12 points

-- Convert to points/units (returns number)
local pt = glu.sp_to_pt(sp)    -- 12.0
local cm = glu.sp_to_unit(sp, "cm")
local mm = glu.sp_to_unit(sp, "mm")

-- Min/Max (returns ScaledPoint)
local max = glu.max(sp1, sp2)
local min = glu.min(sp1, sp2)

-- Arithmetic operations
local sum = sp + sp2           -- ScaledPoint + ScaledPoint
local diff = sp - "5mm"        -- ScaledPoint - string
local scaled = sp * 2          -- ScaledPoint * number
local half = sp / 2            -- ScaledPoint / number
local ratio = sp / sp2         -- ScaledPoint / ScaledPoint → number

-- Comparisons
if sp > sp2 then ... end
if sp == glu.sp("12pt") then ... end

-- Properties
print(sp.pt)                   -- value in points (number)
print(sp.sp)                   -- raw scaled point value (number)
print(sp:to_mm())              -- value in mm (number)

See ScaledPoint in the frontend documentation for the full API.

Logging

Logging follows Go’s slog pattern with structured key-value pairs:

local glu = require("glu")

glu.debug("Processing file", "filename", "test.txt", "size", 1024)
glu.info("Document created", "pages", 10)
glu.warn("Font not found", "name", "Arial")
glu.error("Failed to load", "path", "/missing.ttf")

Constants

Name Value Description
glu.factor 65535 Scaled points per DTP point

glu.node Module

The glu.node module provides access to the fundamental typesetting nodes. Nodes are linked together to form horizontal and vertical lists.

Creating Nodes

local node = require("glu.node")

local glyph = node.new("glyph")
local glue = node.new("glue")
local kern = node.new("kern")
local disc = node.new("disc")
local penalty = node.new("penalty")
local rule = node.new("rule")
local hlist = node.new("hlist")
local vlist = node.new("vlist")
local image = node.new("image")
local lang = node.new("lang")
local startstop = node.new("startstop")

Common Node Attributes

All nodes have these attributes:

Name R/W Type Description
next R/W Node Next node in list
prev R/W Node Previous node in list
type R string Node type name
id R number Unique node ID

Glyph Node

A glyph represents a single character or ligature.

Name R/W Type Description
codepoint R/W number Font-specific glyph ID
components R/W string Character composition
width R/W sp Glyph width
height R/W sp Glyph height
depth R/W sp Glyph depth
yoffset R/W sp Vertical offset
hyphenate R/W boolean Part of hyphenatable word

Glue Node

Glue represents elastic space that can stretch or shrink.

Name R/W Type Description
width R/W sp Natural width
stretch R/W sp Maximum stretch
shrink R/W sp Maximum shrink
stretch_order R/W number Stretch infinity level
shrink_order R/W number Shrink infinity level
local glu = require("glu")
local node = require("glu.node")

local glue = node.new("glue")
glue.width = glu.sp("12pt")
glue.stretch = glu.sp("6pt")
glue.shrink = glu.sp("4pt")
glue.stretch_order = node.fil  -- infinite stretch

Kern Node

A kern is a small fixed space, typically for letter spacing.

Name R/W Type Description
kern R/W sp Kern width

Penalty Node

A penalty indicates a potential break point with a cost.

Name R/W Type Description
penalty R/W number Break cost (-10000 to 10000)
width R/W sp Width if broken here

Rule Node

A rule draws a filled rectangle.

Name R/W Type Description
width R/W sp Rule width
height R/W sp Rule height
depth R/W sp Rule depth
pre R/W string PDF code before rule
post R/W string PDF code after rule
hide R/W boolean Don’t draw the rectangle

HList Node

A horizontal list contains nodes arranged horizontally.

Name R/W Type Description
width R/W sp Total width
height R/W sp Maximum height
depth R/W sp Maximum depth
list R/W Node First node in list
glue_set R/W number Glue ratio (stretch/shrink)
glue_sign R/W number 0=normal, 1=stretch, 2=shrink
glue_order R/W number Infinity level
shift R/W sp Vertical shift
badness R/W number Line badness

VList Node

A vertical list contains nodes arranged vertically.

Name R/W Type Description
width R/W sp Total width
height R/W sp Total height
depth R/W sp Depth
list R/W Node First node in list
glue_set R/W number Glue ratio
glue_sign R/W number 0=normal, 1=stretch, 2=shrink
shift_x R/W sp Horizontal shift

Disc Node

A discretionary break point for hyphenation.

Name R/W Type Description
pre R/W Node Nodes before break
post R/W Node Nodes after break
replace R/W Node Replacement if no break
penalty R/W number Additional penalty

Image Node

Name R/W Type Description
width R/W sp Image width
height R/W sp Image height
page R/W number Page number (PDF)
used R/W boolean Already output

List Operations

-- Link nodes
glyph.next = glue
glue.next = kern

-- Insert node after another
local head = node.insert_after(head, current, new_node)

-- Insert node before another
local head = node.insert_before(head, current, new_node)

-- Delete node from list
local head = node.delete(head, node_to_remove)

-- Copy entire list
local copy = node.copy_list(head)

-- Get last node
local last = node.tail(head)

-- Get dimensions
local width, height, depth = node.dimensions(head)

-- Debug string
local str = node.string(head)

Packing

Pack a node list into a box:

local glu = require("glu")
local node = require("glu.node")

-- Pack to natural width
local hlist = node.hpack(head)

-- Pack to specific width (stretches/shrinks glue)
local hlist = node.hpack_to(head, glu.sp("200pt"))

-- Pack vertically
local vlist = node.vpack(head)

Glue Order Constants

Name Value Description
node.normal 0 Finite glue
node.fil 1 First order infinity
node.fill 2 Second order infinity
node.filll 3 Third order infinity

glu.font Module

The glu.font module provides font instances for text shaping.

Creating a Font

local glu = require("glu")
local pdf = require("glu.pdf")
local font = require("glu.font")

-- Load a face first (via glu.pdf module)
local pw = pdf.new("out.pdf")
local face = pw:load_face("fonts/CrimsonPro-Regular.ttf")

-- Create font instance with size
local fnt = font.new(face, glu.sp("12pt"))

Font Attributes

Name R Type Description
size R sp Font size
space R sp Space width
space_stretch R sp Space stretchability
space_shrink R sp Space shrinkability

Shaping Text

The shape method converts text into atoms (glyph metrics):

local atoms = fnt:shape("Hello World")

-- With OpenType features
local atoms = fnt:shape("fficacy", "+liga", "+kern")

for i, atom in ipairs(atoms) do
    print(atom.components, atom.advance)
end

Atom Attributes

Name R Type Description
advance R sp Advance width
height R sp Glyph height
depth R sp Glyph depth
codepoint R number Font-specific glyph ID
components R string Character(s) represented
is_space R boolean Is a space character
hyphenate R boolean Part of word
kern_after R sp Kerning after this glyph

Example: Manual Typesetting

local glu = require("glu")
local pdf = require("glu.pdf")
local font = require("glu.font")
local node = require("glu.node")

-- Low-level typesetting example
local pw = pdf.new("manual.pdf")
local face = pw:load_face("fonts/CrimsonPro-Regular.ttf")
local fnt = font.new(face, glu.sp("12pt"))

-- Shape text into atoms
local atoms = fnt:shape("Hello World", "+kern", "+liga")

-- Convert atoms to glyph nodes
local head = nil
local tail = nil

for _, atom in ipairs(atoms) do
    local g
    if atom.is_space then
        g = node.new("glue")
        g.width = atom.advance
        g.stretch = glu.sp("3pt")
        g.shrink = glu.sp("2pt")
    else
        g = node.new("glyph")
        g.codepoint = atom.codepoint
        g.width = atom.advance
        g.height = atom.height
        g.depth = atom.depth
        g.components = atom.components
    end

    if head == nil then
        head = g
        tail = g
    else
        tail.next = g
        tail = g
    end
end

-- Pack into hlist
local hlist = node.hpack(head)

-- Pack into vlist for output
local vlist = node.vpack(hlist)

-- Output to page
-- (use glu.frontend for actual page output)