The glu.frontend module
The glu.frontend module provides a high-level interface for creating PDF documents. It allows you to create and manipulate text, tables, and other elements. Fonts are organized in font families, and settings like font weight and style are handled automatically.
local frontend = require("glu.frontend")Module functions
| Name | Parameters | Description |
|---|---|---|
frontend.new() |
filename (string) | Create a new document |
frontend.text() |
[settings] (table) | Create a new Text object |
frontend.fontsource() |
options (table) | Create a new font source |
frontend.table() |
options (table) | Create a new Table object |
frontend.color() |
options (table) | Create a new Color object |
frontend.sp() |
points (number) | Create a ScaledPoint from points |
frontend.sp_string() |
dimension (string) | Create a ScaledPoint from string |
frontend.parse_svg() |
filename (string) | Parse an SVG file into an SVGDocument |
frontend.parse_svg_string() |
svg (string) | Parse an SVG string into an SVGDocument |
ScaledPoint
ScaledPoint is a type-safe dimension type that prevents confusion between raw numbers and dimensions. It wraps the internal scaled point unit (1/65536 of a DTP point).
Creating ScaledPoints
local frontend = require("glu.frontend")
-- From points (number)
local sp1 = frontend.sp(72) -- 72 points = 1 inch
-- From string with unit
local sp2 = frontend.sp_string("2cm")
local sp3 = frontend.sp_string("10mm")
local sp4 = frontend.sp_string("1in")
local sp5 = frontend.sp_string("72pt")Arithmetic operations
ScaledPoints support arithmetic with other ScaledPoints, strings with units, and numbers (interpreted as points):
local sp = frontend.sp_string("10cm")
-- ScaledPoint operations
local sp2 = frontend.sp_string("2cm")
local result = sp + sp2 -- ScaledPoint + ScaledPoint
local diff = sp - sp2 -- ScaledPoint - ScaledPoint
-- Mixed operations with strings
local wider = sp + "5mm" -- ScaledPoint + string
local narrower = sp - "1cm" -- ScaledPoint - string
-- Operations with numbers (interpreted as points)
local adjusted = sp + 72 -- adds 72 points (1 inch)
local half = sp / 2 -- ScaledPoint / number
local doubled = sp * 2 -- ScaledPoint * number
local neg = -sp -- unary minus
-- Division of two ScaledPoints returns a ratio (number)
local ratio = sp / sp2 -- returns 5.0Comparison operations
local a = frontend.sp_string("5cm")
local b = frontend.sp_string("50mm")
print(a == b) -- true (5cm equals 50mm)
print(a < b) -- false
print(a <= b) -- trueConversion methods
local sp = frontend.sp_string("2.54cm") -- 1 inch
-- Properties (read-only)
print(sp.pt) -- 72.0 (value in points)
print(sp.sp) -- 4718592 (raw scaled point value)
-- Methods
print(sp:to_pt()) -- 72.0
print(sp:to_mm()) -- 25.4
print(sp:to_cm()) -- 2.54
print(sp:to_in()) -- 1.0
-- String representation
print(sp) -- "72pt"Usage with dimensions
All dimension parameters (page sizes, positions, font sizes, etc.) accept ScaledPoints directly:
local page = doc:new_page()
page.width = frontend.sp_string("210mm")
page.height = frontend.sp_string("297mm")
-- Arithmetic in output_at
local margin = frontend.sp_string("2cm")
local y = frontend.sp_string("28cm")
page:output_at(margin, y - "1cm", vlist)
-- Reading dimensions returns ScaledPoints
local w = page.width -- ScaledPoint
local h = vlist.height -- ScaledPoint
local new_y = y - h - "5mm" -- arithmetic with ScaledPoints and stringsDocument
local frontend = require("glu.frontend")
local doc = frontend.new("output.pdf")Attributes
| Name | R/W | Type | Description |
|---|---|---|---|
title |
R/W | string | Document title |
author |
R/W | string | Document author |
subject |
R/W | string | Document subject |
creator |
R/W | string | Creator application |
keywords |
R/W | string | Document keywords |
format |
R/W | string | PDF format (see below) |
language |
W | Language | Default document language |
additional_xml_metadata |
R/W | string | Additional XMP metadata |
Format values: "PDF", "PDF/A-3b", "PDF/X-3", "PDF/X-4", "PDF/UA"
Methods
| Name | Parameters | Returns | Description |
|---|---|---|---|
new_font_family() |
name (string) | FontFamily | Create a new font family |
find_font_family() |
name (string) | FontFamily | Find an existing font family |
create_text() |
- | Text | Create a new Text object |
format_paragraph() |
text, width, [options] | VList, info | Format text into a paragraph |
build_table() |
table | VList array | Build a table |
define_color() |
name (string), color | - | Define a named color |
get_color() |
spec (string) | Color | Get color by name or CSS value |
get_language() |
name (string) | Language | Get language for hyphenation |
new_page() |
- | Page | Create a new page |
load_imagefile() |
filename, [page], [box] | Imagefile | Load an image or PDF file |
create_image_node() |
imagefile, [page], [box] | ImageNode | Create image node for placement |
load_colorprofile() |
filename | ColorProfile | Load ICC color profile |
attach_file() |
options (table) | - | Attach a file to the PDF |
create_svg_node() |
svgdoc, width, height, [ff] | SVGNode | Create an SVG node for placement |
finish() |
- | - | Finalize the PDF |
format_paragraph()
local vlist, info = doc:format_paragraph(text, width, options)| Parameter | Type | Description |
|---|---|---|
text |
Text object | The text to format |
width |
dimension | The width of the paragraph |
options |
table | Optional settings (see below) |
Options table:
| Name | Type | Description |
|---|---|---|
leading |
dimension | Line height |
font_size |
dimension | Font size |
font_family |
FontFamily | Font family to use |
language |
Language | Language for hyphenation |
halign |
string | Horizontal alignment |
indent_left |
dimension | Left indentation |
indent_left_rows |
integer | Lines that get indent_left, read only together with it: n > 0 the first n lines, n < 0 all but the first -n, 0 all lines. Default 1 |
indent_right |
dimension | Right indentation: narrows the lines without moving where they start |
indent_right_rows |
integer | Lines that get indent_right, as indent_left_rows |
linebreak_tolerance |
number | Largest adjustment ratio a line may have, default 4. 0 keeps the default here; the text setting takes 0 |
linebreak_emergency_stretch |
dimension | Stretch for every line in a last pass of the line breaker, see the text setting |
linebreak_hyphen_penalty |
integer | Penalty for a line that ends at a hyphenation point, default 50. 0 keeps the default here; the text setting takes 0 |
Text
local txt = frontend.text()Attributes
| Name | R/W | Type | Description |
|---|---|---|---|
items |
W | table | Set content items (strings, Text, etc.) |
settings |
R | proxy | Access settings via txt.settings.key |
Methods
| Name | Parameters | Returns | Description |
|---|---|---|---|
append() |
item, … | self | Append strings, Text, VList, or Table |
set() |
key, value | self | Set a single setting (chainable) |
apply() |
table | self | Set multiple settings at once (chainable) |
Setting text properties
There are several ways to set text properties:
-- 1. In the constructor (recommended)
local txt = frontend.text({
font_family = ff,
font_size = "12pt",
color = "black"
})
-- 2. Using set() with chaining
local txt = frontend.text()
:set("font_family", ff)
:set("font_size", "12pt")
:set("color", "black")
-- 3. Using settings proxy - for individual assignments
local txt = frontend.text()
txt.settings.font_family = ff
txt.settings.font_size = "12pt"
txt.settings.color = "black"
-- 4. Using apply() to add settings later
local txt = frontend.text()
txt:apply({
font_family = ff,
font_size = "12pt"
})You can set items directly or use append:
-- Using items property
txt.items = {"Hello, ", boldText, "!"}
-- Using append (chainable)
txt:append("Hello, ", boldText, "!")Text settings
| Name | Type | Description |
|---|---|---|
font_family |
FontFamily | The font family to use |
font_size |
dimension | Font size (e.g. 12 or "12pt") |
font_weight |
string or int | "regular", "bold", or 100-900 |
font_style |
string | "normal" or "italic" |
color |
string or Color | Text color |
leading |
dimension | Line height |
halign |
string | "left", "right", "center", "justified" |
valign |
string | "top", "middle", "bottom" |
indent_left |
dimension | Left indentation of every line; wins over the indent_left option of format_paragraph, whose indent_left_rows still chooses the lines |
margin_left |
dimension | Left margin |
margin_right |
dimension | Right margin |
margin_top |
dimension | Top margin |
margin_bottom |
dimension | Bottom margin |
padding_left |
dimension | Left padding |
padding_right |
dimension | Right padding |
padding_top |
dimension | Top padding |
padding_bottom |
dimension | Bottom padding |
background_color |
string or Color | Background color |
hyperlink |
string | URL for hyperlink |
underline |
boolean | Underline text |
overline |
boolean | Line above the text |
line_through |
boolean | Strikethrough text; the three lines combine, false takes one away |
leader |
string | Leader pattern (e.g. ".") for TOC dot fills |
tab_stops |
table | Tab stops, see below |
horizontal_scale |
number | Glyph width factor, 0.9 draws at 90% (Tz) |
font_expansion |
number | Share a glyph may stretch or shrink, 0.05 = 5%; only justified text uses it |
linebreak_tolerance |
number | Largest adjustment ratio a line may have, how far its spaces may stretch as a multiple of their stretchability. Default 4; 0 takes only lines that fit exactly |
linebreak_emergency_stretch |
dimension | Stretch added to every line in a last pass, which runs only when the paragraph cannot be broken within the tolerance (TeX’s \emergencystretch). Default 0 |
linebreak_hyphen_penalty |
integer | Penalty for a line that ends at a hyphenation point. Default 50, higher values hyphenate less |
indent_left_steps |
table | A left inset that changes from row to row, see below. Replaces indent_left |
indent_right_steps |
table | The same for the right inset |
background_area |
string | Band an inline background_color covers: "em_box" (default) or "ascent_descent", the font’s ascent and descent as browsers paint it |
record_line_trims |
boolean | Record each line’s trims against the paragraph’s font, for CSS text-box-trim. Read at the paragraph level |
The line breaker settings are read at the paragraph level, on the Text that format_paragraph gets. In Markdown and HTML documents, the CSS properties -bag-linebreak-tolerance and -bag-linebreak-hyphen-penalty set the first and the last.
indent_left_steps and indent_right_steps take a list of steps, each { inset = <dimension>, rows = <integer> } or the pair { <dimension>, <integer> }. The steps follow each other from the first line: each step indents its number of lines, and the lines after the last step are not indented. A step with rows = 0 holds for every line from there on. The staircase can follow the edge of something beside the paragraph, such as an image:
txt:apply({
-- three lines 2cm in, two lines 1cm in, then full width
indent_left_steps = { { "2cm", 3 }, { "1cm", 2 } },
})Example:
local txt = frontend.text({
font_family = ff,
font_size = "12pt",
color = "rebeccapurple"
})
txt:append("Hello, ")
-- Nested styled text
local bold = frontend.text({ font_weight = "bold" })
bold:append("world")
txt:append(bold, "!")Leaders (TOC dot fills)
The leader setting creates a repeating pattern that fills the available space — typically used for table-of-contents entries where dots connect the heading text to the page number.
local tocline = frontend.text({ font_family = ff, font_size = "10pt" })
-- The leader pattern is shaped, packed into an HList, and repeated
local dots = frontend.text({ leader = " . " })
tocline:append("Chapter 1", dots, "5")
local vl = doc:format_paragraph(tocline, "12cm", { halign = "left" })This produces: Chapter 1 . . . . . . . . . 5
Leaders use LeaderAligned mode by default: the dot pattern is placed on a global grid so that dots in different lines are vertically aligned. The leader glue has stretch order filll (3), which dominates over the line-end glue (fill, order 2). This pushes the page number to the right margin.
Tab stops
tab_stops is a list of stops. A tab character in the text advances to the first stop past the text before it; once the stops run out a tab keeps its usual width. An entry is either a position (a left stop) or a table. A position is a dimension or a percentage of the line width, such as "100%" for a stop at the end of the line.
| Field | Type | Description |
|---|---|---|
position |
dimension or percentage | Distance from the paragraph’s start edge |
align |
string | "left" (default), "right", "center" or "decimal" |
separator |
string | What a decimal stop aligns on, "." if not set |
leader |
string | Pattern repeated across the tab (e.g. " . ") |
-- A contents line: the title at 12 mm, the page number flush right
-- at the measure, dots in between
local toc = frontend.text({
tab_stops = { "12mm", { position = "100%", align = "right", leader = " . " } },
})
toc.items = { "1\tIntroduction\t3\n2\tThe line breaker\t17" }
local vl = doc:format_paragraph(toc, "150mm", { font_family = ff })
-- Figures lined up on the decimal comma
local prices = frontend.text({
tab_stops = { { position = "60mm", align = "decimal", separator = "," } },
})Reading txt.settings.tab_stops returns the long form, a list of tables with position, align and, if set, separator and leader.
FontFamily
local ff = doc:new_font_family("body")Methods
| Name | Parameters | Returns | Description |
|---|---|---|---|
add_member() |
fontsource, weight, style | self | Add a font to the family |
FontSource
local fs = frontend.fontsource({
location = "path/to/font.ttf",
index = 0, -- optional, for font collections
size_adjust = 1.0, -- optional
features = {"kern", "liga"} -- optional OpenType features
})
ff:add_member(fs, "regular", "normal")
ff:add_member(fs_bold, "bold", "normal")
ff:add_member(fs_italic, "regular", "italic")
-- Alternative: table-based add_member
ff:add_member({ source = fs, weight = 400, style = "normal" })Weight values: "regular", "bold", or numbers 100 - 900
Style values: "normal", "italic"
Page
local page = doc:new_page()Attributes
| Name | R/W | Type | Description |
|---|---|---|---|
width |
R/W | ScaledPoint | Page width (returns ScaledPoint on read) |
height |
R/W | ScaledPoint | Page height (returns ScaledPoint on read) |
Methods
| Name | Parameters | Returns | Description |
|---|---|---|---|
output_at() |
x, y, vlist | self | Place VList at position |
shipout() |
- | - | Finalize the page |
Example:
local page = doc:new_page()
page.width = "21cm"
page.height = "29.7cm"
page:output_at("2cm", "27cm", vlist)
page:shipout()Table
local tbl = frontend.table({
max_width = "15cm",
stretch = true,
font_family = ff,
font_size = "10pt",
leading = "12pt"
})Methods
| Name | Parameters | Returns | Description |
|---|---|---|---|
set_columns() |
widths (table) | self | Set column widths |
add_row() |
- | TableRow | Add a new row |
TableRow Methods
| Name | Parameters | Returns | Description |
|---|---|---|---|
add_cell() |
- | TableCell | Add a new cell |
TableRow Attributes
| Name | R/W | Type | Description |
|---|---|---|---|
min_height |
R/W | dimension | Least height of the row; a row with more content grows |
fixed_height |
R/W | dimension | Exact height of the row; content that does not fit is drawn past it |
valign |
R/W | string | Vertical alignment of the row’s cells: "top", "middle", "bottom" |
break_inside |
R/W | boolean | Allow the row to split across pages |
A row with fixed_height ignores min_height and never splits.
TableCell Attributes
| Name | R/W | Type | Description |
|---|---|---|---|
halign |
R/W | string | Horizontal alignment |
valign |
R/W | string | Vertical alignment |
colspan |
R/W | integer | Column span |
rowspan |
R/W | integer | Row span |
padding_left |
W | dimension | Left padding |
padding_right |
W | dimension | Right padding |
padding_top |
W | dimension | Top padding |
padding_bottom |
W | dimension | Bottom padding |
border_left_width |
W | dimension | Left border width |
border_right_width |
W | dimension | Right border width |
border_top_width |
W | dimension | Top border width |
border_bottom_width |
W | dimension | Bottom border width |
TableCell Methods
| Name | Parameters | Returns | Description |
|---|---|---|---|
set_contents() |
item, … | self | Set cell contents |
Example:
local tbl = frontend.table({ max_width = "15cm" })
tbl:set_columns({ "5cm", "10cm" })
local row = tbl:add_row()
local cell1 = row:add_cell()
cell1:set_contents("Column 1")
cell1.halign = "left"
local cell2 = row:add_cell()
cell2:set_contents("Column 2")
local vlists = doc:build_table(tbl)
for _, vl in ipairs(vlists) do
page:output_at("2cm", y, vl)
endVList
A VList (vertical list) is the result of formatting text or building tables. It contains the typeset content ready for placement on a page.
local vlist, info = doc:format_paragraph(text, "15cm", { leading = "14pt" })
page:output_at("2cm", "27cm", vlist)Attributes
| Name | R/W | Type | Description |
|---|---|---|---|
width |
R | ScaledPoint | Width of the VList |
height |
R | ScaledPoint | Height of the VList |
depth |
R | ScaledPoint | Depth of the VList |
paint_last |
R/W | boolean | Paint the VList after everything else on its page. It keeps its place, which still makes room for it, and is painted there once the page’s other objects are out, the way CSS paints a float over the backgrounds beside it |
paint_last_scope |
R/W | boolean | Paint the paint_last nodes inside this VList right after it, not at the end of the page |
The dimension attributes return ScaledPoint values, allowing arithmetic operations:
local vlist = doc:format_paragraph(text, "15cm")
local y = frontend.sp_string("28cm")
-- Position next element below the vlist
local next_y = y - vlist.height - "5mm"
page:output_at("2cm", next_y, next_vlist)Color
local red = frontend.color({
model = "rgb",
r = 1.0, g = 0.0, b = 0.0
})
local cyan = frontend.color({
model = "cmyk",
c = 1.0, m = 0.0, y = 0.0, k = 0.0
})
local gray = frontend.color({
model = "gray",
g = 0.5
})
doc:define_color("myred", red)
local col = doc:get_color("myred")
local blue = doc:get_color("#0000ff")Language
local en = doc:get_language("en") -- English
local de = doc:get_language("de") -- German
local fr = doc:get_language("fr") -- FrenchUse in format_paragraph options or text settings for hyphenation.
Imagefile
Represents a loaded image or PDF file.
local imgfile = doc:load_imagefile("image.pdf", 1, "/MediaBox")Attributes
| Name | R | Type | Description |
|---|---|---|---|
filename |
R | string | Original filename |
format |
R | string | Image format |
width |
R | integer | Image width in pixels |
height |
R | integer | Image height in pixels |
scale_x |
R | number | X scale factor |
scale_y |
R | number | Y scale factor |
number_of_pages |
R | integer | Number of pages (for PDFs) |
internal_name |
R | string | Internal PDF name |
ImageNode
An image node ready for placement on a page.
local imgnode = doc:create_image_node(imgfile, 1, "/MediaBox")
imgnode.width = "5cm"
imgnode.height = "3cm"
-- Use node.vpack to create a VList
local node = require("glu.node")
local vl = node.vpack(imgnode)
page:output_at("2cm", "25cm", vl)Attributes
| Name | R/W | Type | Description |
|---|---|---|---|
width |
R/W | ScaledPoint | Image width (returns ScaledPoint on read) |
height |
R/W | ScaledPoint | Image height (returns ScaledPoint on read) |
crop |
R/W | table | Region of the image to show, { x, y, width, height }; nil shows all |
crop is in the units of the image’s natural size: pixels for a bitmap, points of the page box for a PDF page. x and y are measured from the top left corner. The region fills the node’s box, and the image is clipped to it, as CSS object-view-box does in HTML:
-- the top left quarter of the image
imgnode.crop = { x = 0, y = 0, width = imgfile.width / 2, height = imgfile.height / 2 }ColorProfile
ICC color profile for PDF/A and PDF/X compliance.
local cp = doc:load_colorprofile("AdobeRGB1998.icc")
cp.identifier = "AdobeRGB1998"
cp.registry = "Adobe"
cp.info = "Adobe RGB (1998)"
cp.condition = "RGB"
cp.colors = 3Attributes
| Name | R/W | Type | Description |
|---|---|---|---|
identifier |
R/W | string | Profile identifier |
registry |
R/W | string | Registry name |
info |
R/W | string | Profile description |
condition |
R/W | string | Color condition (e.g. “RGB”) |
colors |
R/W | integer | Number of color components |
SVGDocument
A parsed SVG document, ready for rendering.
local svg_doc = frontend.parse_svg("diagram.svg")
-- or
local svg_doc = frontend.parse_svg_string(svg_string)Attributes
| Name | R | Type | Description |
|---|---|---|---|
width |
R | number | SVG document width in user units |
height |
R | number | SVG document height in user units |
SVGNode
An SVG node ready for placement on a page. Internally, an SVGNode is a Rule node: the SVG is converted to PDF drawing operators (paths, colors, transforms) and stored in the rule’s Pre field. The drawing is clipped to the SVG’s viewport, so nothing outside it is drawn, as in a browser; an SVG without any size is not clipped. It works with node.vpack(), page:output_at(), and element:set_background().
local svg_node = doc:create_svg_node(svg_doc, "80mm", 0)
svg_node.width = "60mm" -- override width
local node = require("glu.node")
page:output_at("2cm", "26cm", node.vpack(svg_node))create_svg_node()
| Parameter | Type | Description |
|---|---|---|
svgdoc |
SVGDocument | The parsed SVG document |
width |
dimension | Output width (0 = calculate from height and aspect ratio) |
height |
dimension | Output height (0 = calculate from width and aspect ratio) |
ff |
FontFamily or table | Optional fallback font family for <text> elements, or an options table with font_family and crop |
crop shows a region of the SVG, { x, y, width, height } in the units of its width and height, measured from its top left corner. The region becomes the SVG’s viewport, its natural size and aspect ratio:
local quarter = doc:create_svg_node(svg_doc, "5cm", 0, {
crop = { x = 0, y = 0, width = svg_doc.width / 2, height = svg_doc.height / 2 },
})If a FontFamily is provided, <text> elements in the SVG are rendered using the document’s font system. Font-family names in the SVG are resolved against the document’s registered font families; ff is used as fallback when no match is found. Without a FontFamily, text elements are skipped.
Attributes
| Name | R/W | Type | Description |
|---|---|---|---|
width |
R/W | ScaledPoint | Node width (returns ScaledPoint on read) |
height |
R/W | ScaledPoint | Node height (returns ScaledPoint on read) |