wiki/templates/utils/notes.typ
2026-01-18 16:46:44 +01:00

186 lines
4.7 KiB
Typst

/*
Keep track of a running note counter, and associated notes.
*/
#let note_state_prefix = "notes-"
#let note_default_group = "default"
#let note_default_display_fn(note) = {
h(0pt, weak: true)
super([[#note.index]])
}
#let add_note(
// The location of the note. This is used to derive
// what the note counter should be for this note.
loc,
// The note itself.
text,
// The offset which will be added to the 0-based counter index
// when the index stored in the state.
offset: 1,
// the display function that creates the returned content from put.
// Put can't return a pure index number because the counter
// and state updates need to output content.
display: note_default_display_fn,
// The state group to track notes in.
// A group acts independent (both counter and set of notes)
// from other groups.
group: note_default_group
) = {
let s = state(note_state_prefix + group, ())
let c = counter(note_state_prefix + group)
// find any existing note that hasn't been printed yet,
// containing the exact same text:
let existing = s.at(loc).filter((n) => n.text == text)
// If we found an existing note use that,
// otherwise the note is the current location's counter + offset
// and the given text (the counter is 0-based, we want 1-based indices)
let note = if existing.len() > 0 {
existing.first()
} else {
(text: text, index: c.at(loc).first() + offset, page: loc.page())
}
// If we didn't find an existing index, increment the counter
// and add the note to the "notes" state.
if existing.len() == 0 {
c.step()
s.update(notes => notes + (note,))
}
// Output the note marker
display(note)
}
// get notes at specific location
#let get_notes(loc, group: note_default_group) = {
state(note_state_prefix + group, ()).at(loc)
}
// Reset the note group to empty.
// Note: The counter does not reset by default.
#let reset_notes(group: note_default_group, reset_counter: false) = {
if reset_counter {
counter(note_state_prefix + group).update(0)
}
state(note_state_prefix + group, ()).update(())
}
//
// Helpers for nicer in-document ergonomics
//
#let render_notes(fn, group: note_default_group, reset: true, reset_counter: false) = {
locate(loc => fn(get_notes(loc, group: group)))
if reset {
reset_notes(group: group, reset_counter: reset_counter)
}
}
// Create a new note at the current location
#let note(note, ..args) = {
locate((loc) => add_note(loc, note, ..args))
}
// The quick-start option that outputs something useful by default.
// This is a sane-defaults call to `render_notes`.
#let notes(
size: 8pt,
font: "Roboto",
line: line(length: 100%, stroke: 1pt + gray),
padding: (top: 3mm),
alignment: bottom,
numberings: "1",
group: note_default_group,
reset: true,
reset_counter: false
) = {
let render(notes) = {
if notes.len() > 0 {
set align(alignment)
block(breakable: false, pad(..padding, {
if line != none { line }
set text(size: size, font: font)
for note in notes {
[/ #text(font: "Roboto Mono")[[#numbering(numberings, note.index)]]: #note.text]
}
}))
}
}
render_notes(group: group, reset: reset, reset_counter: reset_counter, render)
}
//
// Examples
//
#set page(height: 5cm, width: 15cm)
= Example
- Having a flexible note-keeping system is important #note[Citation needed]
- It's pretty easy to implement with Typst #note[Fact]
- Everyone really likes the name "Typst" #note[Citation needed]
// Print notes since last print:
#notes()
#pagebreak()
These notes won't be printed on this page, so they accumulate onto next page.
- Hello World #note[Your first program]
- Foo Bar #note[Does not serve beverages]
#pagebreak()
- Orange #note[A Color]
- Blue #note[A Color]
// Notice that the "Citation needed" gets a new index
// because we've re-used it since we printed the initial "Citation needed"
- All colors are great #note[Citation needed]
#notes()
#pagebreak()
#set page(
height: 8cm,
footer: notes(padding: (bottom: 5mm)),
margin: (rest: 5mm, bottom: 3cm)
)
= Footnotes
It is of course also possible to place the notes _in_ the page footer.
This is the way to implement footnotes.
- Black#note[Debateably a color]
- White#note[Debateably a color]
#pagebreak()
- Orange#note[A Color]
- Blue#note[A Color]
- Purple#note[Also a color]
#pagebreak()
= Note groups
This page still has footnotes (using the default note group)
but we can also name a new group for custom notes.
#let mynote = note.with(group: "custom")
#let mynotes = notes.with(
group: "custom",
font: "Comic Neue",
size: 12pt,
numberings: "a.",
line: none,
alignment: right + top
)
- This is in it's own group#mynote[Custom]
- Regular footnote here#note[Regular footnote]
- Same custom note#mynote[Custom]
#mynotes()