On this page:
6.1 How Book Publishing Works
6.2 Creating the Book Configuration
6.3 Writing a Render Function
6.4 Writing a Typst Template
6.5 Organizing Your Book
6.5.1 Filtering Content
6.6 Building the Book
6.7 Handling Custom Elements
6.8 Tips for Book Production
9.1

6 Tutorial: Creating a Book from Your Blog🔗ℹ

This tutorial shows how to compile your blog posts into a print-ready PDF book using Camp’s Typst integration. We’ll create a book configuration, write a render function, and build a PDF containing selected posts from your site.

This tutorial assumes you have a working Camp site with a blog collection. If you don’t have one yet, work through the Building a Camp Site tutorial first.

6.1 How Book Publishing Works🔗ℹ

Camp’s book publishing is intentionally minimal. Camp handles:

  • Selecting pages from your site’s collections based on filters you define

  • Organizing them into parts and chapters

  • Passing them to your render function

  • Writing the output to a .typ file and calling typst compile

Everything else—the book’s metadata, layout, typography, title page, table of contents—is your responsibility. You control all of this through your render function and Typst template. This approach gives you complete flexibility while keeping Camp’s book system simple.

Before starting, make sure you have Typst installed. You can install it via your package manager (brew install typst on macOS) or download it from the Typst website.

6.2 Creating the Book Configuration🔗ℹ

Book configurations use the #lang camp/book language. Create a file called "essays.book.rkt" in your site’s root directory:

"essays.book.rkt"

#lang camp/book
 
render-with = "(mysite/render render-book)"
output-folder = "_output"
includes = ["template.typ"]
 
[[parts]]
name = "Essays"
collections = ["posts"]
date-from = 2024-01-01
date-to = 2024-12-31

The configuration has three required fields:

  • render-with: A readable datum specifying the module path and identifier of your render function. The function receives a list of parts and returns a complete Typst document as a string.

  • output-folder: Where to write the .typ and .pdf files.

  • parts: At least one part, each with a name and content sources.

The optional includes field lists files or directories to copy to the output folder before Typst compilation. Use this for templates, fonts, or images referenced by your Typst code.

6.3 Writing a Render Function🔗ℹ

Your render function receives a list of parts. Each part is a hash with 'name (string) and 'chapters (list). Each chapter is a hash with 'slug (string) and 'doc (a Punct document).

Create "render.rkt" (or add to your existing one):

"render.rkt"

#lang racket/base
 
(require camp
         punct/doc
         racket/string)
 
(provide render-book)
 
(define (render-book parts)
  (string-append
   ;; Preamble: import template and configure the book
   #<<TYPST
#import "template.typ": book
 
#show: book.with(
  title: "Selected Essays",
  authors: ("Your Name",),
  year: 2024,
)
 
TYPST
   ;; Render each part
   (string-append*
    (for/list ([part (in-list parts)])
      (render-part part)))))
 
(define (render-part part)
  (define name (hash-ref part 'name))
  (define chapters (hash-ref part 'chapters))
  (string-append
   (format "= ~a\n\n" name)
   (string-append*
    (for/list ([ch (in-list chapters)])
      (render-chapter ch)))))
 
(define (render-chapter ch)
  (define slug (hash-ref ch 'slug))
  (define doc (hash-ref ch 'doc))
  (define title (or (meta-ref doc 'title) slug))
  (string-append
   ;; Chapter heading with label for cross-references
   (format "== ~a <~a>\n\n" title slug)
   ;; Render the document body to Typst
   (camp-doc->typst doc)
   "\n\n"))

The key function is camp-doc->typst, which converts a Punct document to Typst markup. Your render function controls everything else: the preamble, part headings, chapter headings, and how content is assembled.

6.4 Writing a Typst Template🔗ℹ

The template controls your book’s visual design. Create "template.typ":

"template.typ"

#let book(

  title: none,

  authors: (),

  year: none,

  body,

) = {

  set document(title: title, author: authors.join(", "))

  set text(font: "Linux Libertine", size: 11pt)

  set page(paper: "us-trade", margin: (inside: 0.875in, outside: 0.625in))

  set par(leading: 0.65em, first-line-indent: 1.5em, justify: true)

 

  // Title page

  align(center + horizon)[

    #text(size: 24pt, weight: "bold")[#title]

    #v(1em)

    #text(size: 14pt)[#authors.join(" and ")]

    #v(2em)

    #text(size: 12pt)[#year]

  ]

 

  pagebreak()

  outline(title: "Contents")

  pagebreak()

 

  // Chapter headings

  show heading.where(level: 1): it => {

    pagebreak(weak: true)

    v(2em)

    text(size: 18pt, weight: "bold", it.body)

    v(1em)

  }

 

  show heading.where(level: 2): it => {

    v(1.5em)

    text(size: 14pt, weight: "bold", it.body)

    v(0.5em)

  }

 

  body

}

Your render function imports this template and applies it using #show: book.with(...). All book metadata (title, authors, etc.) flows through the render function into the template—not through Camp’s configuration.

6.5 Organizing Your Book🔗ℹ

Parts can include content from collections (filtered by date and taxonomy) or explicit pages:

[[parts]]

name = "2024 Essays"

collections = ["posts"]

date-from = 2024-01-01

date-to = 2024-12-31

taxonomies = { series = "essays" }

 

[[parts]]

name = "Technical Articles"

collections = ["posts"]

taxonomies = { tags = ["programming", "racket"] }

 

[[parts]]

name = "Appendix"

pages = ["appendix-a.poly.pm", "appendix-b.poly.pm"]

6.5.1 Filtering Content🔗ℹ

Parts that pull from collections can filter pages by:

  • Date range: date-from and date-to select posts within a time period

  • Taxonomies: The taxonomies table filters by metadata values. A string value requires an exact match; an array matches if any value is present.

For example, to include posts tagged with either "programming" or "racket":

taxonomies = { tags = ["programming", "racket"] }

6.6 Building the Book🔗ℹ

Build your book with:

  raco camp print essays.book.rkt

This command:

  1. Loads your site configuration

  2. Gathers and filters pages into parts and chapters

  3. Copies includes to the output folder

  4. Calls your render function

  5. Writes the result to a .typ file

  6. Runs typst compile to produce a PDF

The output files use the book file’s name (e.g., "_output/essays.typ" and "_output/essays.pdf").

6.7 Handling Custom Elements🔗ℹ

If your blog posts use custom elements, you can handle them in two ways:

In your render function: Pass a custom render class to camp-doc->typst that handles your elements specially.

In your Typst template: Define Typst functions for elements like term and page-ref:

#let term(body) = emph(body)

#let term_definition(body, name: none) = strong(body)

For page references, the Typst output includes labels (e.g., <my-post>) that you can reference with @my-post or #link(<my-post>)[custom text].

6.8 Tips for Book Production🔗ℹ

Blog posts often need adjustments for print:

  • Links: External URLs don’t work in print. Consider footnotes with URLs or styling links to show the URL inline.

  • Images: Web images may need higher resolution for print.

  • Length: Very short posts may feel sparse as chapters. Consider grouping related short posts.

See the Typst documentation for details on advanced formatting, running headers, page numbers, and other print production features.