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 stretchKern 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)
endAtom 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)