# Clojure-Typst A Clojure-inspired Lisp that compiles to Typst, bringing s-expressions, functional programming, macros, and REPL-driven development to modern typesetting. ## Dual Mode: Hiccup + Raw Escapes Two modes coexist. Use whichever fits the moment. ```clojure ;; HICCUP - when you need programmatic structure [:heading {:level 1} "Title"] ;; RAW - when you just want to write #t"= Title" ``` ### Mixing Freely ```clojure (def title "My Document") (def data [{:name "Alice" :value 100} {:name "Bob" :value 200}]) (defn calculate-total [rows] (reduce + (map :value rows))) (defn render-table [rows] [:table {:columns 2} (for [row rows] (list (:name row) (:value row)))]) [;; Hiccup for structured parts [:heading {:level 1} title] ;; Raw escape for prose-heavy sections #t" This is just regular Typst content. I can write *bold* and _italic_ without all the brackets. Much nicer for prose. - Bullet one - Bullet two " ;; Back to hiccup for programmatic stuff (render-table data) ;; Raw with interpolation #t"The total is ~(calculate-total data)." ;; Nested: raw inside hiccup [:block {:inset "1em" :fill "gray.lighten(90%)"} #t"This is a _sidebar_ with easy formatting."] ;; Nested: hiccup inside raw #t" = Section Here's a dynamic table: ~(render-table data) And back to prose. "] ``` ```typst #heading(level: 1)[My Document] This is just regular Typst content. I can write *bold* and _italic_ without all the brackets. Much nicer for prose. - Bullet one - Bullet two #table(columns: 2, [Alice], [100], [Bob], [200]) The total is 300. #block(inset: 1em, fill: gray.lighten(90%))[This is a _sidebar_ with easy formatting.] = Section Here's a dynamic table: #table(columns: 2, [Alice], [100], [Bob], [200]) And back to prose. ``` ### Raw Escape Syntax ```clojure ;; Short string #t"= Heading" ;; Multi-line block #t" Multiple lines of typst content " ;; Content with quotes (must escape) #t"He said \"hello\" and *left*." ``` ### Interpolation Inside Raw Using `~()` - the unquote concept from Clojure's syntax-quote: ```clojure ;; Simple expression (def answer 42) #t"The value is ~answer." ``` ```typst The value is 42. ``` ```clojure ;; Hiccup expression returns content (def url "https://example.com") (def label "here") #t"Click ~([:link {:src url} label]) to continue." ``` ```typst Click #link("https://example.com")[here] to continue. ``` ```clojure ;; Conditional (def done true) #t"Status: ~(if done 'Complete' 'Pending')" ``` ```typst Status: Complete ``` ```clojure ;; Loop (def items [{:name "Apples"} {:name "Oranges"}]) #t" = Items ~(for [i items] (str '- ' (:name i) '\n')) " ``` ```typst = Items - Apples - Oranges ``` ## Hiccup Syntax All elements follow standard Hiccup: ```clojure [:tag {attrs} children...] ``` - **tag**: Element type (keyword) - **attrs**: Optional map of attributes - **children**: Content (strings, nested elements, or expressions) ### Element Categories | Category | Examples | Children | |----------|----------|----------| | Content elements | `heading`, `strong`, `emph`, `block` | Display content | | Source elements | `link`, `image`, `bibliography` | `link` has display text; others have none | | List elements | `list`, `enum`, `table` | Each child = one item/cell | | Container elements | `figure` | Single content child | | Math | `math` | Math expression string | | References | `ref`, `cite` | None | | Code | `raw` | Code string to display | | Rules | `set`, `show` | Configuration | ### Reserved Attributes | Attr | Used by | Purpose | |------|---------|---------| | `:src` | `link`, `image`, `bibliography` | Source URL/path | | `:label` | Any element | Makes element referenceable | | `:caption` | `figure` | Figure caption text | | `:target` | `ref` | Label to reference | | `:keys` | `cite` | Citation key(s) | | `:element` | `set`, `show` | Element type to configure | | `:block` | `math`, `raw` | Display as block (default: inline) | | `:lang` | `raw` | Code language for syntax highlighting | ### Basic Elements ```clojure ;; Heading [:heading {:level 2} "My Section"] ;; => #heading(level: 2)[My Section] ;; Inline formatting ["Hello " [:strong "world"] " and " [:emph "goodbye"]] ;; => Hello #strong[world] and #emph[goodbye] ;; Link [:link {:src "https://typst.app"} "Typst"] ;; => #link("https://typst.app")[Typst] ;; Image [:image {:src "diagram.png" :width "50%"}] ;; => #image("diagram.png", width: 50%) ``` ### Lists and Tables Children are direct content, not wrapped in `:item` or `:cell` tags: ```clojure ;; List [:list "First" "Second" "Third"] ;; => #list([First], [Second], [Third]) ;; Table [:table {:columns 2} [:strong "Header 1"] [:strong "Header 2"] "Data 1" "Data 2"] ;; => #table(columns: 2, [#strong[Header 1]], [#strong[Header 2]], [Data 1], [Data 2]) ``` ### Figures Caption is an attribute; child element is the content: ```clojure [:figure {:caption "Architecture diagram" :label :fig/arch} [:image {:src "arch.png" :width "80%"}]] ;; => #figure(image("arch.png", width: 80%), caption: [Architecture diagram]) ``` ### Math ```clojure ;; Inline math (default) [:math "x^2 + y^2"] ;; => $x^2 + y^2$ ;; Block/display math [:math {:block true} "sum_(i=0)^n i"] ;; => $ sum_(i=0)^n i $ ``` ### Code Display ```clojure ;; Inline code [:raw "let x = 1"] ;; => `let x = 1` ;; Code block with language [:raw {:lang "python" :block true} "print('hello')"] ;; => ```python print('hello') ``` ``` ### References and Citations ```clojure [:ref {:target :fig/arch}] ;; => @fig:arch [:cite {:keys [:smith2020]}] ;; => @smith2020 ``` ### Document Settings ```clojure ;; Set rules [:set {:element :page :paper "a4" :margin "2cm"}] ;; => #set page(paper: "a4", margin: 2cm) [:set {:element :text :font "New Computer Modern" :size "11pt"}] ;; => #set text(font: "New Computer Modern", size: 11pt) ;; Show rules [:show {:element :heading} [:set {:element :text :fill "blue"}]] ;; => #show heading: set text(fill: blue) ``` ### Spacing and Paragraphs ```clojure [:h "2em"] ;; => #h(2em) [:v "1em"] ;; => #v(1em) [:parbreak] ;; paragraph break [:linebreak] ;; line break within paragraph ``` ## Visualization Three levels of visualization, all mappable to hiccup. ### Built-in Shapes ```clojure [:rect {:width "2cm" :height "1cm" :fill "blue"} "Label"] ;; => #rect(width: 2cm, height: 1cm, fill: blue)[Label] [:circle {:radius "0.5cm" :fill "green"}] ;; => #circle(radius: 0.5cm, fill: green) [:line {:start [0 0] :end [100 50] :stroke "2pt"}] ;; => #line(start: (0pt, 0pt), end: (100pt, 50pt), stroke: 2pt) [:polygon {:fill "yellow"} [0 0] [1 0] [0.5 1]] ;; => #polygon(fill: yellow, (0pt, 0pt), (1pt, 0pt), (0.5pt, 1pt)) ``` ### CeTZ - Canvas Drawing For complex custom drawings. Like TikZ for LaTeX. ```clojure [:cetz/canvas [:cetz/line [0 0] [2 2]] [:cetz/circle [1 1] {:radius 0.5}] [:cetz/rect [0 0] [2 1] {:fill "blue"}]] ``` Compiles to: ```typst #import "@preview/cetz:0.4.2" #cetz.canvas({ import cetz.draw: * line((0, 0), (2, 2)) circle((1, 1), radius: 0.5) rect((0, 0), (2, 1), fill: blue) }) ``` ### Fletcher - Diagrams & Flowcharts High-level diagram DSL for architecture, flowcharts, state machines. ```clojure [:fletcher/diagram {:node-stroke "1pt" :spacing "2em"} [:node [0 0] "Start"] [:edge "->"] [:node [1 0] "Process"] [:edge "->"] [:node [2 0] "End"]] ``` Node shapes: `:circle`, `:diamond`, `:pill`, `:hexagon`, `:cylinder` Edge styles: `"->"`, `"->>"`, `"--"`, `"<->"`, `"hook->"` ```clojure ;; Flowchart example [:fletcher/diagram {:node-stroke "1pt" :node-fill "white" :spacing "3em"} [:node [0 0] "Start" {:shape :pill}] [:node [0 1] "Input Data"] [:node [0 2] "Valid?" {:shape :diamond}] [:node [1 2] "Error" {:fill "red.lighten(80%)"}] [:node [0 3] "Process"] [:node [0 4] "End" {:shape :pill}] [:edge [0 0] [0 1] "->"] [:edge [0 1] [0 2] "->"] [:edge [0 2] [1 2] "->" {:label "no"}] [:edge [0 2] [0 3] "->" {:label "yes"}] [:edge [1 2] [0 1] "->" {:bend -40}] [:edge [0 3] [0 4] "->"]] ``` ### DSL Helpers Build higher-level abstractions: ```clojure (defn service [id name & {:keys [x y color] :or {color "white"}}] [:node [x y] name {:id id :shape :pill :fill color}]) (defn database [id name & {:keys [x y color] :or {color "gray.lighten(80%)"}}] [:node [x y] name {:id id :shape :cylinder :fill color}]) (defn connects [from to & {:keys [label style] :or {style "->"}}] [:edge from to style (when label {:label label})]) [:fletcher/diagram {:spacing "3em"} (service :auth "Auth" :x 0 :y 0) (service :users "Users" :x 1 :y 0) (database :pg "PostgreSQL" :x 0 :y 1) (database :redis "Redis" :x 1 :y 1 :color "red.lighten(80%)") (connects :auth :pg) (connects :users :redis :label "cache")] ``` ## Macros It's a Lisp - of course we have macros. ### Content Macros ```clojure ;; Define a reusable content pattern (defmacro defcomponent [name args & body] `(defn ~name ~args (list ~@body))) ;; Conditional content sections (defmacro when-content [test & body] `(when ~test (list ~@body))) ;; Use it (def show-abstract true) (def abstract-text "This paper presents...") (when-content show-abstract [:heading {:level 2} "Abstract"] abstract-text) ``` ```typst == Abstract This paper presents... ``` ### DSL Macros ```clojure ;; Shorthand macros for references (defmacro ref [id] `[:ref {:target ~id}]) (defmacro cite [& keys] `[:cite {:keys [~@keys]}]) (ref :fig/arch) (cite :smith2020 :jones2021) ``` ```typst @fig:arch @smith2020 @jones2021 ``` ```clojure ;; Math helper functions (defn sum [var from to body] (str "sum_(" var "=" from ")^" to " " body)) (defn frac [num denom] (str "(" num ") / (" denom ")")) (defn pow [base exp] (str base "^" exp)) ;; Math block macro (defmacro math-block [& body] `[:math {:block true} (str ~@body)]) (math-block (sum "i" 0 "n" (frac (pow "x" "i") "i!"))) ``` ```typst $ sum_(i=0)^n (x^i) / (i!) $ ``` ### Template Macros ```clojure ;; Helper functions for template rendering (defn render-author [{:keys [name affil]}] [:block {:align "center"} [:strong name] [:linebreak] [:emph affil]]) (defn render-authors [authors] (map render-author authors)) (defn render-abstract [text] [[:heading {:level 2} "Abstract"] [:block {:inset "1em"} [:emph text]]]) ;; Template macro - captures body and prepends document setup (defmacro deftemplate [name params & preamble] `(defmacro ~name [~@params & ~'body] (concat ~@preamble ~'body))) (deftemplate ieee-paper [title authors abstract] [[:set {:element :page :paper "us-letter" :columns 2}] [:set {:element :text :size "10pt"}] [:heading {:level 1 :align "center"} title] (render-authors authors) (render-abstract abstract)]) ;; Use the template (ieee-paper "Neural Networks for Fun and Profit" [{:name "Alice" :affil "MIT"}] "We present a novel approach..." [:heading {:level 2} "Introduction"] "The problem of machine learning..." [:heading {:level 2} "Methods"] "Our method consists of...") ``` ```typst #set page(paper: "us-letter", columns: 2) #set text(size: 10pt) #heading(level: 1, align: center)[Neural Networks for Fun and Profit] #block(align: center)[#strong[Alice] \ #emph[MIT]] == Abstract #block(inset: 1em)[#emph[We present a novel approach...]] == Introduction The problem of machine learning... == Methods Our method consists of... ``` ## Full Example ```clojure (ns my-paper.core (:require [clojure-typst.core :refer :all])) (def authors [{:name "Alice Chen" :affiliation "MIT" :email "alice@mit.edu"} {:name "Bob Smith" :affiliation "Stanford" :email "bob@stanford.edu"}]) (def results [{:method "Baseline" :accuracy 0.72} {:method "Ours" :accuracy 0.94}]) (defn author-card [{:keys [name affiliation email]}] [:block {:inset "1em"} [:strong name] [:linebreak] [:emph affiliation] [:linebreak] [:link {:src (str "mailto:" email)} email]]) (defn results-table [data] [:table {:columns 2 :align ["left" "right"]} [:strong "Method"] [:strong "Accuracy"] (for [{:keys [method accuracy]} data] (list method (format "%.1f%%" (* 100 accuracy))))]) (def paper [[:set {:element :page :paper "a4"}] [:set {:element :text :font "Linux Libertine"}] [:heading {:level 1} "A Very Important Paper"] [:block {:align "center"} (interpose [:h "2em"] (map author-card authors))] #t" = Abstract We present a novel approach to solving important problems. Our method achieves *state-of-the-art* results on several benchmarks while maintaining computational efficiency. = Introduction The problem of _important things_ has long challenged researchers. Previous approaches @citation1 @citation2 have made progress but fundamental limitations remain. " [:figure {:caption "System architecture overview." :label :fig/arch} [:image {:src "architecture.png" :width "80%"}]] #t" Our method works by first processing the input through the encoder (see @fig:arch). = Results " (results-table results) #t" As shown in the table above, our method significantly outperforms the baseline. = Conclusion We have demonstrated that our approach is ~(if (> (:accuracy (last results)) 0.9) \"highly effective\" \"somewhat effective\"). " [:bibliography {:src "refs.bib"}]]) (compile-to-typst paper "paper.typ") ``` ```typst #set page(paper: "a4") #set text(font: "Linux Libertine") #heading(level: 1)[A Very Important Paper] #block(align: center)[ #block(inset: 1em)[ #strong[Alice Chen] \ #emph[MIT] \ #link("mailto:alice@mit.edu")[alice@mit.edu] ] #h(2em) #block(inset: 1em)[ #strong[Bob Smith] \ #emph[Stanford] \ #link("mailto:bob@stanford.edu")[bob@stanford.edu] ] ] = Abstract We present a novel approach to solving important problems. Our method achieves *state-of-the-art* results on several benchmarks while maintaining computational efficiency. = Introduction The problem of _important things_ has long challenged researchers. Previous approaches @citation1 @citation2 have made progress but fundamental limitations remain. #figure( image("architecture.png", width: 80%), caption: [System architecture overview.] ) Our method works by first processing the input through the encoder (see @fig:arch). = Results #table( columns: 2, align: (left, right), [#strong[Method]], [#strong[Accuracy]], [Baseline], [72.0%], [Ours], [94.0%] ) As shown in the table above, our method significantly outperforms the baseline. = Conclusion We have demonstrated that our approach is highly effective. #bibliography("refs.bib") ``` ## Rules Summary 1. **`#t"..."`** - raw Typst content, returned as-is 2. **`~(...)`** inside raw - interpolate Clojure expression 3. **`[:tag ...]`** - hiccup, compiled to Typst function calls 4. **Strings in hiccup** - become Typst content directly 5. **Sequences flatten** - `(for ...)`, `(map ...)`, `(list ...)` results merge into parent | Situation | Use | |-----------|-----| | Prose, paragraphs, natural writing | Raw `#t"..."` | | Tables, figures, structured layout | Hiccup | | Loops, conditionals, data-driven | Hiccup or interpolated raw | | Components, reusable parts | Hiccup functions | **The principle:** Structure when you need it, prose when you don't.