Install
openclaw skills install @bezkom/typst-bookWrite and typeset richly illustrated books with Typst — text, CeTZ vector art, AI-generated images, and full layout control to PDF.
openclaw skills install @bezkom/typst-bookCreate richly illustrated books using Typst, a modern markup-based typesetting system. Combines prose, vector illustrations (CeTZ), AI-generated images, and full layout control into polished PDF/PNG output.
# Linux x86_64 musl static binary (works on most systems)
curl -sL https://github.com/typst/typst/releases/latest/download/typst-x86_64-unknown-linux-musl.tar.xz -o /tmp/typst.tar.xz
mkdir -p /tmp/typst-dl && tar xf /tmp/typst.tar.xz -C /tmp/typst-dl
mkdir -p ~/.local/bin
cp /tmp/typst-dl/typst-x86_64-unknown-linux-musl/typst ~/.local/bin/typst
chmod +x ~/.local/bin/typst
# Ensure ~/.local/bin is in PATH
export PATH="$HOME/.local/bin:$PATH"
typst --version # should print v0.15.0+
typst --version # v0.13+ minimum, v0.15+ recommended for CeTZ 0.5+
typst fonts # list available fonts
Before writing, decide:
book-project/
main.typ # Entry point, imports chapters
book.typ # Book template/helpers (chapter function, styling)
chapters/
ch01.typ
ch02.typ
...
images/ # AI-generated or external images (PNG, JPEG, SVG)
illustrations/ # CeTZ illustration modules (optional, for complex reusable art)
output/ # Generated PDFs and PNGs
Create book.typ with the reusable styling and helper functions:
#import "@preview/cetz:0.5.2": canvas, draw
// --- Color palette ---
#let palette = (
primary: rgb("#2c3e50"),
accent: rgb("#e74c3c"),
warm: rgb("#f39c12"),
cool: rgb("#3498db"),
muted: rgb("#95a5a6"),
paper: rgb("#fdfdfd"),
)
// --- Page setup ---
#let book-page(width: 21cm, height: 29.7cm, margin: (x: 2.5cm, y: 2.5cm)) = {
set page(width: width, height: height, margin: margin)
}
// --- Chapter helper ---
#let chapter(title, subtitle: none) = {
pagebreak(weak: true)
v(2cm)
block(width: 100%, {
align(center)[
#text(size: 28pt, weight: "bold", fill: palette.primary)[#title]
]
if subtitle != none {
v(0.4cm)
align(center)[#text(size: 14pt, fill: palette.muted)[#subtitle]]
}
})
v(1cm)
}
// --- Figure helper with consistent styling ---
#let illustration(body, caption: none) = {
if caption != none {
figure(alignment(center)[#body], caption: caption)
} else {
align(center)[#body]
}
v(0.8em)
}
// --- Decorative divider ---
#let divider() = {
v(0.5em)
align(center)[#text(fill: palette.muted)[◆ ◆ ◆]]
v(0.5em)
}
main.typ:
#import "book.typ": *
#show: book-page
#set text(font: "DejaVu Serif", size: 11pt, lang: "en")
#set par(justify: true, leading: 0.8em)
#include "chapters/ch01.typ"
#include "chapters/ch02.typ"
chapters/ch01.typ:
#import "../book.typ": *
#chapter[Chapter One: The Beginning]
Once upon a time, in a workshop filled with gears and steam, lived Ada.
#illustration(
canvas({
import draw
draw.circle((0, 0), radius: 2, fill: rgb("#ffd93d"))
draw.circle((-0.6, 0.4), radius: 0.25, fill: black)
draw.circle((0.6, 0.4), radius: 0.25, fill: black)
draw.line((-0.5, -0.6), (0.5, -0.6), stroke: 2pt + black)
}),
caption: [Ada's first smile]
)
Use the right tool for each illustration:
#import "@preview/cetz:0.5.2": canvas, draw
#canvas({
import draw
// Trees
draw.line((0, 0), (0, 3), stroke: 4pt + rgb("#8b4513"))
draw.circle((0, 4), radius: 1.5, fill: rgb("#2ecc71"))
// Clouds
draw.circle((-3, 6), radius: 0.8, fill: rgb("#ecf0f1"))
draw.circle((-2, 6), radius: 1.0, fill: rgb("#ecf0f1"))
draw.circle((-1, 6), radius: 0.7, fill: rgb("#ecf0f1"))
})
CeTZ uses a coordinate system where (0,0) is bottom-left by default. All coordinates are in CeTZ units (not cm/pt). Key functions:
draw.line(from, to, stroke: ..) — linesdraw.circle(center, radius: r, fill: ..) — circlesdraw.rect(corner, width: w, height: h, fill: ..) — rectanglesdraw.content(point, [text]) — inline text at a pointdraw.curve(..points) — smooth curves through pointsdraw.arc(center, start: .., stop: .., radius: r) — arcsUse image_generate tool to create illustrations, save to images/ directory, then embed:
#figure(
image("images/forest-scene.png", width: 80%),
caption: [The enchanted forest at dusk]
)
Recommended image_generate settings for book illustrations:
size: "1024x1024" for spot illustrationssize: "1536x1024" for full-page landscape scenessize: "1024x1536" for full-page portrait scenesquality: "high" for print-qualityoutputFormat: "png" for lossless quality#box(width: 100%, height: 3cm, {
let colors = (rgb("#e74c3c"), rgb("#e67e22"), rgb("#f1c40f"), rgb("#2ecc71"), rgb("#3498db"), rgb("#9b59b6"))
for (i, c) in colors.enumerate() {
place(center + horizon, rect(width: 100%/colors.len(), height: 100%, fill: c))
}
})
#figure(
image("illustrations/diagram.svg", width: 75%),
caption: [System architecture]
)
Typst natively supports SVG, PNG, JPEG, and GIF formats.
# Compile to PDF
typst compile main.typ output/book.pdf
# Compile to PNG (one per page)
typst compile --format png --ppi 150 main.typ "output/page-{p}.png"
# Watch mode for iterative editing
typst watch main.typ output/book.pdf
Always compile from the project root directory so relative paths resolve correctly:
cd book-project && typst compile main.typ output/book.pdf
After compiling:
image tool to review layout qualitytypst compile --format png --ppi 150 main.typ "output/preview-{p}.png"
# Then use image tool on preview-1.png, preview-2.png, etc.
= Heading, == Subheading — headings (number of = = level)*bold*, _italic_ — text styling- item — bullet list+ item — numbered list[link](url) — links`code` — inline code--- — em dash, ~ — non-breaking space#set page(width: 21cm, height: 29.7cm, margin: 2cm) — page setup#set text(font: "DejaVu Serif", size: 12pt) — typography#set par(justify: true, leading: 0.8em) — paragraph settings#pagebreak() — forced page break#v(2cm) / #h(1cm) — vertical/horizontal space#align(center)[content] — alignment#box[..], #block[..] — inline/block containers#grid(columns: 2, ..items) — multi-column layouts#place(center + horizon, ..) — absolute positioning#figure(body, caption: [text]) — numbered figure with caption#figure(image("file.png", width: 80%), caption: [desc]) — image figure#outline(title: [Table of Contents]) — auto-generated TOC#let name = value — variable binding#let func(x) = { .. } — function definition#if condition { .. } else { .. } — conditionals#for item in items { .. } — iteration#include "file.typ" — include another file#import "module.typ": func1, func2 — import from module#show heading: it => [custom heading] — restyle elements#show figure.where(kind: image): it => [..] — restyle specific figures#set heading(numbering: "1.") — auto-number headings#import "@preview/cetz:0.5.2": canvas, draw
#canvas({
import draw
// Shapes
draw.line((0, 0), (5, 5), stroke: 2pt + red)
draw.circle((2, 2), radius: 1, fill: blue, stroke: 1pt + dark-blue)
draw.rect((0, 0), (4, 2), fill: green, stroke: black)
draw.square((1, 1), size: 2, fill: yellow)
draw.ellipse((3, 3), rx: 2, ry: 1, fill: purple)
draw.curve((0, 0), (1, 2), (2, 0), (3, 2), stroke: 1pt)
draw.arc((2, 2), start: 0deg, stop: 180deg, radius: 2)
// Text at point
draw.content((2, 4), [Label], fill: red)
// Groups
draw.group(name: "scene", {
draw.circle((0, 0), radius: 1, fill: blue)
draw.circle((2, 0), radius: 1, fill: red)
})
// Transformations
draw.rotate(45deg, {
draw.rect((0, 0), (4, 1), fill: orange)
})
draw.scale(2, {
draw.circle((0, 0), radius: 1, fill: teal)
})
})
#canvas(length: 1cm, { .. }) to set unit scale.typst fonts to list available fonts. Common: DejaVu Serif, DejaVu Sans, FreeSerif, Latin Modern Roman, JetBrains Mono.@preview/ packages on first compile. Requires network access..typ source file, not the output path. Use --root for absolute paths.#set page(numbering: "1") for Arabic, "i" for Roman, none to skip.#set page(numbering: "i") before main content, then #set page(numbering: "1") at chapter 1.#set heading(numbering: "1.1.a") for "1.1.a" style.#pagebreak() creates a blank page (useful for chapter spacing).rgb("#ff000088") — last two hex digits are alpha.gradient.linear(rgb("#ff6b6b"), rgb("#4ecdc4")) for linear gradients.#place(..) for absolute positioning of decorative elements like corner flourishes.When delivering the final book to the user:
MEDIA:<path> in the reply#import "@preview/cetz:0.5.2": canvas, draw
#set page(width: 20cm, height: 20cm, margin: 2cm)
#set text(font: "DejaVu Serif", size: 14pt)
#align(center)[
#v(2cm)
#text(size: 32pt, weight: "bold", fill: rgb("#e74c3c"))[The Tiny Seed]
#v(1cm)
]
#pagebreak()
In a small garden, a tiny seed slept beneath the soil.
#v(0.5cm)
#align(center)[
#canvas({
import draw
draw.rect((0, 0), (6, 3), fill: rgb("#8b4513"))
draw.circle((3, 3.5), radius: 0.3, fill: rgb("#f4a460"))
draw.line((3, 3.5), (3, 5), stroke: 2pt + rgb("#2ecc71"))
draw.circle((3, 5.5), radius: 0.8, fill: rgb("#2ecc71"))
})
]
#v(0.5cm)
One spring morning, the sun whispered, "Wake up, little one."
#pagebreak()
And so the seed began to grow...
Compile:
typst compile book.typ tiny-seed.pdf