This commit is contained in:
2026-01-30 09:11:34 -10:00
commit fd0ec5aabc
5 changed files with 1545 additions and 0 deletions
+391
View File
@@ -0,0 +1,391 @@
# Dual Mode: Hiccup + Raw Escapes
Both systems coexist. Use whichever fits the moment.
## The Two Modes
```clojure
;; HICCUP - when you need programmatic structure
[:heading {:level 1} "Title"]
;; RAW - when you just want to write
#t"= Title"
```
## Mixing Freely
```clojure
[;; 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
[:table {:columns 2}
(for [row data]
(list (:name row) (:value row)))]
;; Raw with interpolation
#t"The answer is ~(calculate-answer data)."
;; Nested: raw inside hiccup (use :block for container)
[: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 results)
And back to prose.
"]
```
## Escape Syntax Options
```clojure
;; Short string
#t"= Heading"
;; Multi-line block
#t"
Multiple lines
of typst content
"
;; Triple-quote for content with quotes
#t"""
He said "hello" and *left*.
"""
;; With explicit delimiters (if we need)
#typst{
Content here with {braces} works fine
}
;; Tagged for syntax highlighting in editors?
#typst/content "..."
```
## Interpolation Inside Raw
Using `~()` - the unquote concept from Clojure's syntax-quote:
```clojure
;; Simple expression
#t"The value is ~(expr)."
;; Hiccup expression returns content
#t"Click ~([:link {:dest url} label]) to continue."
;; Conditional
#t"Status: ~(if done? 'Complete' 'Pending')"
;; Loop
#t"
= Items
~(for [i items]
(str '- ' (:name i) '\n'))
"
;; Or loop returning hiccup (items are direct list children)
#t"
= Items
~[:list (for [i items] (:name i))]
"
```
Same mental model as macro unquote:
```clojure
`(let [x ~expr] ...) ; macro: escape to eval
#t"Value: ~(expr)" ; template: escape to eval
```
## 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 {:dest (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
[[:heading {:level 1} "A Very Important Paper"]
;; Programmatic author list
[:block {:align "center"}
(interpose [:h "2em"]
(map author-card authors))]
;; Prose in raw mode - much nicer to write
#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.
In this work, we propose a new framework that addresses these
limitations directly. Our key contributions are:
+ A novel architecture for processing data
+ Theoretical analysis of convergence properties
+ Extensive empirical evaluation
= Methods
"
;; Back to hiccup for the technical diagram
[:figure {:caption "System architecture overview."}
[:image {:width "80%"} "architecture.png"]]
#t"
Our method works by first processing the input through
the encoder (see @fig:arch). The encoded representation
is then passed to the decoder which produces the output.
= Results
"
;; Programmatic table
(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").
Future work will explore extensions to other domains.
"
;; Bibliography could be generated
[:bibliography "refs.bib"]])
;; Compile it
(compile-to-typst paper "paper.typ")
```
## Macros
It's a Lisp - of course we have macros.
### Standard Code Macros
```clojure
;; Threading for cleaner data transforms
(defmacro -> [x & forms]
...)
(-> data
(filter :active)
(map :name)
(sort))
;; Custom control flow
(defmacro when-let [[binding expr] & body]
`(let [~binding ~expr]
(when ~binding ~@body)))
```
### Content Macros
Macros that expand to hiccup/content:
```clojure
;; Define a reusable content pattern
(defmacro defcomponent [name args & body]
`(defn ~name ~args
(list ~@body))) ; sequences flatten into parent content
;; Conditional content sections
(defmacro when-content [test & body]
`(when ~test
(list ~@body)))
;; Use it
(when-content show-abstract?
[:heading {:level 2} "Abstract"]
abstract-text)
```
### Document Structure Macros
```clojure
;; Academic paper structure
(defmacro paper [& {:keys [title authors abstract] :as opts} & sections]
`[[:heading {:level 1} ~title]
(author-block ~authors)
(when ~abstract
(list
[:heading {:level 2} "Abstract"]
~abstract))
~@sections])
;; Usage
(paper
:title "My Great Paper"
:authors ["Alice" "Bob"]
:abstract "We did cool stuff."
[:heading {:level 2} "Introduction"]
"..."
[:heading {:level 2} "Methods"]
"...")
```
### DSL Macros
Build mini-languages for specific domains:
```clojure
;; Math DSL that's easier to write than raw Typst math
(defmacro math-block [& exprs]
`[:math {:block true}
~(compile-math-dsl exprs)])
(math-block
(sum :i 0 :n
(frac (pow x i) (factorial i))))
;; => $ sum_(i=0)^n x^i / i! $
;; Table DSL
(defmacro deftable [name headers & row-specs]
...)
(deftable results-table
["Method" "Precision" "Recall" "F1"]
:from results
:row (fn [{:keys [method p r f1]}]
[method (fmt p) (fmt r) (fmt f1)]))
;; Figure DSL with auto-numbering
(defmacro figure [id & {:keys [src caption width]}]
`(do
(register-figure! ~id)
[:figure {:label ~id :caption ["Figure " (figure-num ~id) ": " ~caption]}
[:image {:width ~(or width "100%")} ~src]]))
(figure :arch
:src "architecture.png"
:caption "System overview"
:width "80%")
```
### Syntax Extension Macros
```clojure
;; Custom reader syntax for common patterns
;; (would require reader macro support)
;; Shorthand for references
(defmacro ref [id]
`[:ref ~(str "@" (name id))])
;; Shorthand for citations
(defmacro cite [& keys]
`[:cite ~@(map #(str "@" (name %)) keys)])
;; Usage
(ref :fig:arch) ; => @fig:arch
(cite :smith2020 :jones2021) ; => @smith2020 @jones2021
```
### Template Macros
```clojure
;; Define document templates
(defmacro deftemplate [name args & structure]
`(defmacro ~name ~args
~@structure))
(deftemplate ieee-paper [title authors abstract]
[[:set :page {:paper "us-letter" :columns 2}]
[:heading {:level 1 :align "center"} ~title]
(render-authors ~authors)
(render-abstract ~abstract)
~'&body]) ; splice remaining content
;; Use template
(ieee-paper
"Neural Networks for Fun and Profit"
[{:name "Alice" :affil "MIT"}]
"We present..."
[:heading {:level 2} "Introduction"]
"..."
[:heading {:level 2} "Methods"]
"...")
```
### Why Macros Matter Here
| Pattern | Without Macros | With Macros |
|---------|---------------|-------------|
| Repeated structure | Copy-paste hiccup | `defcomponent` |
| Conditional sections | Verbose `if` expressions | `when-content` |
| Domain languages | Manual compilation | DSL macros |
| Document templates | Function composition | Template macros |
| Boilerplate elimination | Nowhere to hide it | Macro it away |
**The power:** Your document language grows to fit your domain.
## Rules
1. **`#t"..."`** - raw Typst content, returned as-is
2. **`~(...)`** inside raw - interpolate Clojure expression (like unquote)
3. **`[:tag ...]`** - hiccup, compiled to Typst function calls
4. **Strings in hiccup** - become Typst content directly
5. **Seqs in hiccup** - flattened (so `map`/`for` just work)
6. **Sequences flatten** - `(for ...)`, `(map ...)`, `(list ...)` results merge into parent
## Why Both?
| 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 |
| Quick one-off formatting | Either, your preference |
**The principle:** Structure when you need it, prose when you don't.
+209
View File
@@ -0,0 +1,209 @@
# Hiccup for Typst Content - Prototype
## The Idea
Hiccup represents markup as nested data structures:
```clojure
[:tag {attrs} & children]
```
## Syntax Mapping
### Clojure-Typst Hiccup → Typst Output
```clojure
;; Heading
[:heading {:level 2} "My Section"]
;; => #heading(level: 2)[My Section]
;; Inline formatting (paragraphs are implicit in Typst)
["Hello " [:strong "world"] " and " [:emph "goodbye"]]
;; => Hello #strong[world] and #emph[goodbye]
;; Lists (children are direct content, not :item tags)
[:list
"First thing"
"Second thing"
[[:strong "Important"] " third thing"]]
;; => #list(
;; [First thing],
;; [Second thing],
;; [#strong[Important] third thing]
;; )
;; Tables (cells are direct content, not :cell tags)
[:table {:columns 3}
"A" "B" "C"
"1" "2" "3"]
;; => #table(columns: 3,
;; [A], [B], [C],
;; [1], [2], [3]
;; )
;; Raw content blocks
[:raw "= Direct Typst Markup\nJust passed through"]
;; Code blocks (use :raw with :lang)
[:raw {:lang "python" :block true} "print('hello')"]
;; => #raw(lang: "python", block: true, "print('hello')")
;; Math
[:math "x^2 + y^2 = z^2"]
;; => $x^2 + y^2 = z^2$
[:math {:block true} "sum_(i=0)^n i = (n(n+1))/2"]
;; => $ sum_(i=0)^n i = (n(n+1))/2 $
;; Images
[:image {:width "50%"} "diagram.png"]
;; => #image("diagram.png", width: 50%)
;; Links
[:link {:dest "https://typst.app"} "Typst"]
;; => #link("https://typst.app")[Typst]
```
## Full Document Example
```clojure
(def my-doc
[[:heading {:level 1} "My Paper"]
"This is the introduction. We discuss "
[:emph "important things"] "."
[:parbreak]
[:heading {:level 2} "Methods"]
"We used the following approach:"
[:list
"Step one"
"Step two"
"Step three"]
[:heading {:level 2} "Results"]
"The result is " [:$ "x = 42"] "."
[:table {:columns 2}
[:strong "Input"] [:strong "Output"]
"1" "1"
"2" "4"
"3" "9"]])
```
**Compiles to:**
```typst
#heading(level: 1)[My Paper]
This is the introduction. We discuss #emph[important things].
#heading(level: 2)[Methods]
We used the following approach:
#list(
[Step one],
[Step two],
[Step three]
)
#heading(level: 2)[Results]
The result is $x = 42$.
#table(columns: 2,
[#strong[Input]], [#strong[Output]],
[1], [1],
[2], [4],
[3], [9]
)
```
## Mixing Code and Content
```clojure
;; Define a reusable component
(defn author-block [name institution]
[:block {:align "center"}
[:strong name]
[:linebreak]
[:emph institution]])
;; Use it with data
(def authors
[{:name "Alice" :inst "MIT"}
{:name "Bob" :inst "Stanford"}])
;; Generate content programmatically
[[:heading "Authors"]
(map #(author-block (:name %) (:inst %)) authors)]
```
## Special Forms for Content
```clojure
;; Conditional content (strings become paragraphs naturally)
(if peer-reviewed?
"This paper was peer reviewed."
"Preprint - not yet reviewed.")
;; Loop over data (items are direct content in lists)
[:list
(for [item items]
(format-item item))]
;; Let bindings for intermediate content
(let [title (get-title data)
abstract (get-abstract data)]
[[:heading title]
abstract])
```
## The Alternative: Lisp for Code Only
If hiccup feels too heavy, we could just use Lisp for logic and escape to raw Typst:
```clojure
(defn factorial [n]
(if (<= n 1)
1
(* n (factorial (dec n)))))
;; Escape to typst content
#typst{
= Results
The factorial of 5 is ~(factorial 5).
Here's a table of factorials:
~(for [i (range 1 11)]
(str "- " i "! = " (factorial i) "\n"))
}
```
## Questions
1. **Implicit paragraphs?** Should adjacent strings auto-wrap in `[:p ...]`?
2. **Shorthand syntax?** Maybe `:#strong` instead of `[:strong ...]`?
3. **Component system?** How fancy do we get with reusable components?
4. **Escaping?** How to include literal `[` or other special chars?
5. **Whitespace handling?** Typst is whitespace-sensitive in content mode
## Verdict?
Hiccup gives us:
- ✅ Pure data representation of documents
- ✅ Full programmatic control
- ✅ Composable components
- ✅ Familiar to Clojure devs
But:
- ❌ More verbose than raw Typst markup
- ❌ Loses Typst's nice content syntax
- ❌ Another layer to learn
**Hybrid approach might be best:** Lisp for code/logic, with easy escape to Typst content syntax when you just want to write prose.
+84
View File
@@ -0,0 +1,84 @@
# Clojure-Typst: A Clojure-style Lisp for Typst
## The Problem
Typst is a modern typesetting system with a nice programming language, but it's not a Lisp. We only like Lisps.
## The Vision
Create a Clojure-inspired Lisp that compiles to Typst, bringing:
- S-expression syntax
- Functional programming idioms
- Macros and metaprogramming
- Immutable-first data structures
- REPL-driven development (maybe?)
## Open Questions
### Syntax & Semantics
- [ ] How closely do we follow Clojure syntax?
- [ ] What Typst constructs map to Lisp naturally?
- [ ] How do we handle Typst's content mode vs code mode distinction?
- [ ] Do we support Clojure-style destructuring?
- [ ] What about namespaces/modules?
### Data Structures
- [ ] Vectors `[]`, maps `{}`, sets `#{}`?
- [ ] How do these map to Typst arrays and dictionaries?
- [ ] Lazy sequences - possible in Typst?
### Interop
- [ ] How do we call Typst functions from our Lisp?
- [ ] How do we emit Typst content/markup?
- [ ] Can we import Typst packages?
### Implementation
- [ ] What language for the compiler? (Clojure? Rust? Typst itself?)
- [ ] AST representation
- [ ] Compilation strategy (source-to-source? interpreter?)
- [ ] Error messages and source maps
## Typst Features to Leverage
- Functions are first-class
- Has closures
- Pattern matching in function args
- Content as a first-class type
- Scripting mode `#` vs content mode
## Example Syntax Ideas
```clojure
; Define a function
(defn greet [name]
(str "Hello, " name "!"))
; Emit typst content
(content
(heading "My Document")
(greet "World"))
; Maybe something like hiccup for content?
[:heading {:level 1} "My Document"]
["Hello, " [:strong "World"] "!"]
```
## Next Steps
1. Study Typst's language semantics more deeply
2. Identify the minimal viable feature set
3. Decide on implementation language
4. Build a basic parser for s-expressions
5. Implement core special forms (def, fn, let, if, do)
6. Add Typst content emission
7. Iterate
## Resources
- [Typst Documentation](https://typst.app/docs)
- [Clojure Reference](https://clojure.org/reference)
- [Make a Lisp](https://github.com/kanaka/mal)
## Notes
(Add brainstorming notes here)
+330
View File
@@ -0,0 +1,330 @@
# Hiccup → Typst Mapping Reference
## Audit of Current Tags
### ✅ Valid - Direct Typst Functions
| Hiccup | Typst | Notes |
|--------|-------|-------|
| `[:heading {:level 2} "Text"]` | `#heading(level: 2)[Text]` | ✅ Works |
| `[:strong "text"]` | `#strong[text]` | ✅ Works |
| `[:emph "text"]` | `#emph[text]` | ✅ Works |
| `[:link {:dest "url"} "text"]` | `#link("url")[text]` | ✅ Works |
| `[:image {:width "50%"} "path.png"]` | `#image("path.png", width: 50%)` | ✅ Works |
| `[:block {:inset "1em"} ...]` | `#block(inset: 1em)[...]` | ✅ Works |
| `[:bibliography "refs.bib"]` | `#bibliography("refs.bib")` | ✅ Works |
### ❌ Invalid - Don't Exist in Typst
| Bad Hiccup | Problem | Fix |
|------------|---------|-----|
| `[:p "text"]` | No paragraph function | Just use strings, paragraphs are implicit |
| `[:item "text"]` | Not a function | Items are args to `list()` |
| `[:cell "text"]` | Not a function | Cells are args to `table()` |
| `[:div ...]` | HTML, not Typst | Use `[:block ...]` |
| `[:span ...]` | HTML, not Typst | Just inline content, or `[:box ...]` |
| `[:aside ...]` | Doesn't exist | Use `[:block ...]` with styling |
| `[:section ...]` | Doesn't exist | Just use headings |
| `[:doc ...]` | Doesn't exist | Top-level is just content |
| `[:table-row ...]` | Doesn't exist | Tables are flat |
### ⚠️ Needs Adjustment
| Current | Problem | Correct |
|---------|---------|---------|
| `[:linebreak]` | Function name | `[:linebreak]``#linebreak()` ✅ actually works |
| `[:h-space "2em"]` | Wrong name | `[:h "2em"]``#h(2em)` |
| `[:figure [:image ...] [:caption ...]]` | Caption isn't a child | See below |
| `[:math "x^2"]` | Need to distinguish inline/block | `$x^2$` vs `$ x^2 $` |
| `[:code {:lang "py"} "..."]` | Function is `raw` | `#raw(lang: "python", "...")` |
| `[:ref :label]` | Takes label not string | `#ref(<label>)` |
| `[:cite :key]` | Takes label | `#cite(<key>)` |
## Corrected Mappings
### Lists
Typst `list` takes content blocks as positional args, not `:item` children:
```clojure
;; OLD (wrong)
[:list
[:item "First"]
[:item "Second"]]
;; NEW (correct)
[:list "First" "Second"]
;; => #list([First], [Second])
;; With formatted items
[:list
[:strong "Important"]
"Regular item"]
;; => #list([#strong[Important]], [Regular item])
;; Nested
[:list
"Top level"
[:list "Nested 1" "Nested 2"]]
```
**Typst output:**
```typst
#list(
[First],
[Second],
)
```
### Tables
Same pattern - cells are positional args:
```clojure
;; OLD (wrong)
[:table {:columns 2}
[:cell "A"] [:cell "B"]
[:cell "1"] [:cell "2"]]
;; NEW (correct)
[:table {:columns 2}
"A" "B"
"1" "2"]
;; => #table(columns: 2, [A], [B], [1], [2])
;; With formatting
[:table {:columns 2}
[:strong "Header 1"] [:strong "Header 2"]
"Data 1" "Data 2"]
```
**Typst output:**
```typst
#table(
columns: 2,
[#strong[Header 1]], [#strong[Header 2]],
[Data 1], [Data 2],
)
```
### Figures
Caption is a named parameter, not a child element:
```clojure
;; OLD (wrong)
[:figure
[:image "arch.png"]
[:caption "Architecture diagram"]]
;; NEW (correct)
[:figure {:caption "Architecture diagram"}
[:image "arch.png"]]
;; => #figure(image("arch.png"), caption: [Architecture diagram])
;; With label for referencing
[:figure {:caption "Architecture" :label :fig:arch}
[:image {:width "80%"} "arch.png"]]
;; => #figure(image("arch.png", width: 80%), caption: [Architecture]) <fig:arch>
```
**Typst output:**
```typst
#figure(
image("arch.png", width: 80%),
caption: [Architecture],
) <fig:arch>
```
### Math
```clojure
;; Inline math
[:$ "x^2 + y^2"]
;; => $x^2 + y^2$
;; Block/display math
[:$$ "sum_(i=0)^n i"]
;; => $ sum_(i=0)^n i $
;; Or with explicit flag
[:math "x^2"] ;; inline by default
[:math {:block true} "sum_(i=0)^n i"]
```
### Code/Raw
```clojure
;; Inline code
[:raw "let x = 1"]
;; => `let x = 1`
;; Code block with language
[:raw {:lang "python" :block true} "print('hello')"]
;; => ```python
;; print('hello')
;; ```
```
**Typst output:**
```typst
#raw("let x = 1")
#raw(lang: "python", block: true, "print('hello')")
```
### References and Citations
```clojure
;; Reference a label
[:ref :fig:arch]
;; => @fig:arch
;; Citation
[:cite :smith2020]
;; => @smith2020
;; Multiple citations
[:cite :smith2020 :jones2021]
;; => @smith2020 @jones2021
```
### Horizontal/Vertical Space
```clojure
[:h "2em"] ;; => #h(2em)
[:v "1em"] ;; => #v(1em)
```
### Page/Document Settings
`set` rules are special - they affect following content:
```clojure
;; Set rules (affects everything after)
[:set :page {:paper "a4" :margin "2cm"}]
;; => #set page(paper: "a4", margin: 2cm)
[:set :text {:font "New Computer Modern" :size "11pt"}]
;; => #set text(font: "New Computer Modern", size: 11pt)
;; Show rules
[:show :heading {:set {:text {:fill "blue"}}}]
;; => #show heading: set text(fill: blue)
```
### Paragraphs
There is no paragraph function. Paragraphs are created by blank lines:
```clojure
;; Just strings, separated by [:parbreak] or blank content
"First paragraph."
[:parbreak]
"Second paragraph."
;; Or in raw mode, just write naturally
#t"
First paragraph.
Second paragraph.
"
```
## Revised Full Example
```clojure
(def my-doc
[;; Set rules at top
[:set :page {:paper "a4"}]
[:set :text {:font "Linux Libertine"}]
[:heading {:level 1} "My Paper"]
"This is the introduction. We discuss "
[:emph "important things"] "."
[:parbreak]
[:heading {:level 2} "Methods"]
"We used the following approach:"
[:list
"Step one"
"Step two"
[:strong "Step three (important)"]]
[:heading {:level 2} "Results"]
"See " [:ref :fig:results] " and " [:ref :tab:data] "."
[:figure {:caption "Results visualization" :label :fig:results}
[:image {:width "80%"} "results.png"]]
[:table {:columns 2 :label :tab:data}
[:strong "Input"] [:strong "Output"]
"1" "1"
"2" "4"
"3" "9"]
"The equation is " [:$ "E = mc^2"] "."
[:$$ "integral_0^infinity e^(-x^2) dif x = sqrt(pi)/2"]
[:bibliography "refs.bib"]])
```
**Compiles to:**
```typst
#set page(paper: "a4")
#set text(font: "Linux Libertine")
#heading(level: 1)[My Paper]
This is the introduction. We discuss #emph[important things].
#heading(level: 2)[Methods]
We used the following approach:
#list(
[Step one],
[Step two],
[#strong[Step three (important)]],
)
#heading(level: 2)[Results]
See @fig:results and @tab:data.
#figure(
image("results.png", width: 80%),
caption: [Results visualization],
) <fig:results>
#table(
columns: 2,
[#strong[Input]], [#strong[Output]],
[1], [1],
[2], [4],
[3], [9],
) <tab:data>
The equation is $E = mc^2$.
$ integral_0^infinity e^(-x^2) dif x = sqrt(pi)/2 $
#bibliography("refs.bib")
```
## Summary of Changes
1. **Removed**: `:p`, `:item`, `:cell`, `:div`, `:span`, `:aside`, `:section`, `:doc`
2. **Lists/Tables**: Children are direct content, not wrapped in `:item`/`:cell`
3. **Figures**: Caption is an attribute, not a child
4. **Math**: Use `:$` for inline, `:$$` for block
5. **Code**: Use `:raw` not `:code`
6. **Space**: Use `:h`/`:v` not `:h-space`
7. **Settings**: `:set` takes element type and attrs
8. **Paragraphs**: Use `:parbreak` or just content flow
+531
View File
@@ -0,0 +1,531 @@
# Visualization & Diagrams
Three levels of visualization in Typst, all mappable to hiccup.
---
## 1. Built-in Shapes
Typst has basic shapes built-in. Simple and direct.
### Rectangle
**Clojure:**
```clojure
[:rect {:width "2cm" :height "1cm" :fill "blue"} "Label"]
```
**Typst:**
```typst
#rect(width: 2cm, height: 1cm, fill: blue)[Label]
```
### Square
**Clojure:**
```clojure
[:square {:size "1cm" :stroke "2pt + red"}]
```
**Typst:**
```typst
#square(size: 1cm, stroke: 2pt + red)
```
### Circle
**Clojure:**
```clojure
[:circle {:radius "0.5cm" :fill "green"}]
```
**Typst:**
```typst
#circle(radius: 0.5cm, fill: green)
```
### Ellipse
**Clojure:**
```clojure
[:ellipse {:width "2cm" :height "1cm"}]
```
**Typst:**
```typst
#ellipse(width: 2cm, height: 1cm)
```
### Polygon
**Clojure:**
```clojure
[:polygon {:fill "yellow"} [0 0] [1 0] [0.5 1]]
```
**Typst:**
```typst
#polygon(fill: yellow, (0pt, 0pt), (1pt, 0pt), (0.5pt, 1pt))
```
### Line
**Clojure:**
```clojure
[:line {:start [0 0] :end [100 50] :stroke "2pt"}]
```
**Typst:**
```typst
#line(start: (0pt, 0pt), end: (100pt, 50pt), stroke: 2pt)
```
### Path
**Clojure:**
```clojure
[:path {:closed true :fill "blue"} [0 0] [10 0] [10 10] [0 10]]
```
**Typst:**
```typst
#path(closed: true, fill: blue, (0pt, 0pt), (10pt, 0pt), (10pt, 10pt), (0pt, 10pt))
```
### Curve (Bézier)
**Clojure:**
```clojure
[:curve
[:move [0 0]]
[:cubic [10 20] [30 20] [40 0]]]
```
**Typst:**
```typst
#curve(
curve.move((0pt, 0pt)),
curve.cubic((10pt, 20pt), (30pt, 20pt), (40pt, 0pt)),
)
```
---
## 2. CeTZ - Canvas Drawing
For complex custom drawings. Like TikZ for LaTeX.
### Basic Canvas
**Clojure:**
```clojure
[:cetz/canvas
[:cetz/line [0 0] [2 2]]
[:cetz/circle [1 1] {:radius 0.5}]
[:cetz/rect [0 0] [2 1] {:fill "blue"}]]
```
**Typst:**
```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)
})
```
### Line with Multiple Points
**Clojure:**
```clojure
[:cetz/line [0 0] [1 0] [1 1] [0 1] {:close true}]
```
**Typst:**
```typst
line((0, 0), (1, 0), (1, 1), (0, 1), close: true)
```
### Arc
**Clojure:**
```clojure
[:cetz/arc [0 0] {:start 0 :stop 90 :radius 1}]
```
**Typst:**
```typst
arc((0, 0), start: 0deg, stop: 90deg, radius: 1)
```
### Bezier Curve
**Clojure:**
```clojure
[:cetz/bezier [0 0] [1 1] [0.5 2] [0.5 -1]]
```
**Typst:**
```typst
bezier((0, 0), (1, 1), (0.5, 2), (0.5, -1))
```
### Content Label in Canvas
**Clojure:**
```clojure
[:cetz/content [1 1] "Label here"]
[:cetz/content [0 0] [:strong "Bold label"]]
```
**Typst:**
```typst
content((1, 1), [Label here])
content((0, 0), [#strong[Bold label]])
```
### Architecture Boxes Example
**Clojure:**
```clojure
(defn box-with-label [pos size label]
(let [[x y] pos
[w h] size
cx (+ x (/ w 2))
cy (+ y (/ h 2))]
(list
[:cetz/rect pos [(+ x w) (+ y h)] {:fill "lightblue" :stroke "black"}]
[:cetz/content [cx cy] label])))
[:cetz/canvas
(box-with-label [0 0] [3 2] "Frontend")
(box-with-label [5 0] [3 2] "Backend")
(box-with-label [10 0] [3 2] "Database")
[:cetz/line [3 1] [5 1] {:mark {:end ">"}}]
[:cetz/line [8 1] [10 1] {:mark {:end ">"}}]]
```
**Typst:**
```typst
#cetz.canvas({
import cetz.draw: *
// Frontend
rect((0, 0), (3, 2), fill: lightblue, stroke: black)
content((1.5, 1), [Frontend])
// Backend
rect((5, 0), (8, 2), fill: lightblue, stroke: black)
content((6.5, 1), [Backend])
// Database
rect((10, 0), (13, 2), fill: lightblue, stroke: black)
content((11.5, 1), [Database])
// Arrows
line((3, 1), (5, 1), mark: (end: ">"))
line((8, 1), (10, 1), mark: (end: ">"))
})
```
---
## 3. Fletcher - Diagrams & Flowcharts
High-level diagram DSL. Best for architecture, flowcharts, state machines.
### Basic Diagram
**Clojure:**
```clojure
[:fletcher/diagram {:node-stroke "1pt" :spacing "2em"}
[:node [0 0] "Start"]
[:edge "->"]
[:node [1 0] "Process"]
[:edge "->"]
[:node [2 0] "End"]]
```
**Typst:**
```typst
#import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge
#diagram(
node-stroke: 1pt,
spacing: 2em,
node((0, 0), [Start]),
edge("-|>"),
node((1, 0), [Process]),
edge("-|>"),
node((2, 0), [End]),
)
```
### Node Shapes
**Clojure:**
```clojure
[:node [0 0] "Default"]
[:node [1 0] "Circle" {:shape :circle}]
[:node [2 0] "Diamond" {:shape :diamond}]
[:node [3 0] "Pill" {:shape :pill}]
[:node [4 0] "Hexagon" {:shape :hexagon}]
[:node [5 0] "Cylinder" {:shape :cylinder}]
```
**Typst:**
```typst
node((0, 0), [Default]),
node((1, 0), [Circle], shape: circle),
node((2, 0), [Diamond], shape: fletcher.shapes.diamond),
node((3, 0), [Pill], shape: fletcher.shapes.pill),
node((4, 0), [Hexagon], shape: fletcher.shapes.hexagon),
node((5, 0), [Cylinder], shape: fletcher.shapes.cylinder),
```
### Edge Styles
**Clojure:**
```clojure
[:edge "->"] ; simple arrow
[:edge "->>"] ; double arrow
[:edge "--"] ; no arrow (line)
[:edge "<->"] ; bidirectional
[:edge "hook->"] ; hook arrow
[:edge "|->"] ; maps-to arrow
```
**Typst:**
```typst
edge("-|>"),
edge("-|>|>"),
edge("--"),
edge("<|-|>"),
edge("hook-|>"),
edge("|-|>"),
```
### Directional Edges
**Clojure:**
```clojure
[:edge "r" "->"] ; go right
[:edge "d" "->"] ; go down
[:edge "u" "->"] ; go up
[:edge "l" "->"] ; go left
[:edge "r,d" "->"] ; right then down
```
**Typst:**
```typst
edge("r", "-|>"),
edge("d", "-|>"),
edge("u", "-|>"),
edge("l", "-|>"),
edge("r,d", "-|>"),
```
### Edge with Label
**Clojure:**
```clojure
[:edge [0 0] [1 0] "->" {:label "HTTP" :label-pos 0.5}]
```
**Typst:**
```typst
edge((0, 0), (1, 0), "-|>", label: [HTTP], label-pos: 0.5),
```
### Curved Edge
**Clojure:**
```clojure
[:edge [0 0] [1 0] "->" {:bend 20}]
```
**Typst:**
```typst
edge((0, 0), (1, 0), "-|>", bend: 20deg),
```
### Flowchart Example
**Clojure:**
```clojure
[:fletcher/diagram {:node-stroke "1pt" :node-fill "white" :spacing "3em"}
;; Nodes
[: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}]
;; Edges
[: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] "->"]]
```
**Typst:**
```typst
#import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge
#diagram(
node-stroke: 1pt,
node-fill: white,
spacing: 3em,
// Nodes
node((0, 0), [Start], shape: fletcher.shapes.pill),
node((0, 1), [Input Data]),
node((0, 2), [Valid?], shape: fletcher.shapes.diamond),
node((1, 2), [Error], fill: red.lighten(80%)),
node((0, 3), [Process]),
node((0, 4), [End], shape: fletcher.shapes.pill),
// Edges
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: -40deg),
edge((0, 3), (0, 4), "-|>"),
)
```
### Architecture Diagram Example
**Clojure:**
```clojure
[:fletcher/diagram {:spacing "4em" :node-stroke "1pt"}
;; Frontend layer
[:node [0 0] "Web App" {:fill "blue.lighten(80%)"}]
[:node [1 0] "Mobile App" {:fill "blue.lighten(80%)"}]
;; API Gateway
[:node [0.5 1] "API Gateway" {:shape :hexagon :fill "green.lighten(80%)"}]
;; Services
[:node [0 2] "Auth Service" {:shape :pill}]
[:node [1 2] "User Service" {:shape :pill}]
[:node [2 2] "Order Service" {:shape :pill}]
;; Data layer
[:node [0 3] "PostgreSQL" {:shape :cylinder}]
[:node [1 3] "Redis" {:shape :cylinder :fill "red.lighten(80%)"}]
[:node [2 3] "MongoDB" {:shape :cylinder :fill "green.lighten(80%)"}]
;; Connections
[:edge [0 0] [0.5 1] "->"]
[:edge [1 0] [0.5 1] "->"]
[:edge [0.5 1] [0 2] "->"]
[:edge [0.5 1] [1 2] "->"]
[:edge [0.5 1] [2 2] "->"]
[:edge [0 2] [0 3] "->"]
[:edge [1 2] [1 3] "->"]
[:edge [2 2] [2 3] "->"]]
```
**Typst:**
```typst
#import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge
#diagram(
spacing: 4em,
node-stroke: 1pt,
// Frontend layer
node((0, 0), [Web App], fill: blue.lighten(80%)),
node((1, 0), [Mobile App], fill: blue.lighten(80%)),
// API Gateway
node((0.5, 1), [API Gateway], shape: fletcher.shapes.hexagon, fill: green.lighten(80%)),
// Services
node((0, 2), [Auth Service], shape: fletcher.shapes.pill),
node((1, 2), [User Service], shape: fletcher.shapes.pill),
node((2, 2), [Order Service], shape: fletcher.shapes.pill),
// Data layer
node((0, 3), [PostgreSQL], shape: fletcher.shapes.cylinder),
node((1, 3), [Redis], shape: fletcher.shapes.cylinder, fill: red.lighten(80%)),
node((2, 3), [MongoDB], shape: fletcher.shapes.cylinder, fill: green.lighten(80%)),
// Connections
edge((0, 0), (0.5, 1), "-|>"),
edge((1, 0), (0.5, 1), "-|>"),
edge((0.5, 1), (0, 2), "-|>"),
edge((0.5, 1), (1, 2), "-|>"),
edge((0.5, 1), (2, 2), "-|>"),
edge((0, 2), (0, 3), "-|>"),
edge((1, 2), (1, 3), "-|>"),
edge((2, 2), (2, 3), "-|>"),
)
```
---
## 4. DSL Helpers
Build higher-level abstractions on top.
**Clojure:**
```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")]
```
**Typst:**
```typst
#diagram(
spacing: 3em,
node((0, 0), [Auth], shape: fletcher.shapes.pill, fill: white),
node((1, 0), [Users], shape: fletcher.shapes.pill, fill: white),
node((0, 1), [PostgreSQL], shape: fletcher.shapes.cylinder, fill: gray.lighten(80%)),
node((1, 1), [Redis], shape: fletcher.shapes.cylinder, fill: red.lighten(80%)),
edge((0, 0), (0, 1), "-|>"),
edge((1, 0), (1, 1), "-|>", label: [cache]),
)
```
---
## Summary
| Use Case | Tool | Hiccup Prefix |
|----------|------|---------------|
| Simple shapes in content | Built-in | `:rect`, `:circle`, `:line` |
| Custom drawings, illustrations | CeTZ | `:cetz/canvas`, `:cetz/line` |
| Flowcharts, architecture | Fletcher | `:fletcher/diagram`, `:node`, `:edge` |
## Sources
- [Typst Visualize Docs](https://typst.app/docs/reference/visualize/)
- [Fletcher Package](https://typst.app/universe/package/fletcher/)
- [CeTZ Package](https://typst.app/universe/package/cetz/)
- [CeTZ Documentation](https://cetz-package.github.io/)