Hello,
For a personal project where i create a template for a student songbook i want to maintain a global dictionary where at each song i “instantiate” i insert a new pair (title, page_number). For example :
Modifying a variable outside of current function scope
Modifying something outside of context.
I’m not sure how to implement this (not even sure if it is possible). My first think goes to #state() function but i don’t really understand the whole principle of it.
So is there a way to do this ?
Thanks in advance for helping.
#state is definitely the right approach.
It took me a little while to understand it too, to encourage you.
Can you explain your workflow in more detail?
What exactly should be saved and when and how should it be called?
What is ‘page_number’? The page on which the current song is saved?
If you explain this to me, I can try to help you with the implementation.
So basically my project revolve around 3 principals files :
style.typ : define the style of each element that constitute a ‘song’
format.typ : format a song based on parameters received, using style defined previoulsy. Here i define a context function called “chant” (song in french) that i can use throughout the project (files found in "chant/*.typ).
utils.typ : define a few function useful for the whole project
Basically i want to create and empty dictionary in utils.typ that contain all page numbers and title of each song to later construct an index table. The page number is the number of the page on which the song is located (already defined in chant() ).
So i am thinking to update this utils.index_dictionnary at the end of chant() function and use it later in the project
It worked, but now i get a bunch of white pages appearing. I supposed it is due to the height of the heading took into account even though it is set to hidden. I will search to prevent this.
edit : set the size of heading to 0pt
show heading: text.with(size: 0pt)
hide(heading(title))
That said, is there a solution involving #state()? I think my problem could be generalized to something like: ‘How to use or modify a global variable effectively?’
This project seems very complex and large, making it difficult to quickly identify why the layout doesn’t converge. There could be many reasons for this, especially in a project of this scale.
However, here’s a Minimal Working Example (MWE) that uses a global state to store songs and their page numbers, which might help you troubleshoot your issue:
This is the minimal chants.typ for storing and getting chant title and page number:
// chants.typ
// State for the songs
#let _state_chants = state("chants", ())
#let _page = counter(page)
// Main function to add a song
#let chant(title: "") = {
// Update state with new song
context {
let new_page = _page.get().first()
_state_chants.update(songs => {
songs.push((
title: title,
page: new_page,
))
songs
})
}
// Show the title as heading
heading(level: 2, title)
}
// Function to create table of contents
#let chant-outline() = {
heading(level: 1, "Liedverzeichnis")
context {
let songs = _state_chants.final()
for song in songs [
#song.title
#box(width: 1fr, repeat[.])
#song.page
#linebreak()
]
}
}
this is an example usage of this file:
//example.typ
#import "chants.typ": *
#set page(
"a6",
numbering: "1",
)
// Outline of the songs
#chant-outline()
#pagebreak()
#chant(title: "Amazing Grace")
Lorem ipsum dolor sit amet...
#pagebreak()
#chant(title: "Hallelujah")
Consectetur adipiscing elit...
In this case the cause is actually fairly clear: you use get() followed by update() instead of update with a callback. Here is a post where I explain in detail what the problem and fix for it is: Why is State Final not "final"? - #2 by SillyFreak
a bit “cleaner” is I think using place(hide(...)) which prevents the header from being considered for layout at all.
But the state based solution Mathemensch shows (using update(songs => ...) looks good :) I don’t think it has convergence problems.The update() is inside a context and depends on a get() but I think it’s fine because it’s a different counter.