I am completely new to Typst and am trying to format a heading level that has the following form: Part One: Fundamentals. The plan is to split this string at the colon (see in particular the commented part of the following code) and place the resulting components with different styles on an additional page:
#show heading.where(level: 1): it => {
pagebreak()
set align(center + horizon)
text(
font: "Frutiger",
size: 12pt,
weight: "regular",
// this is working
it.body
)
v(5em)
// text(
// font: "AvantGarde",
// size: 30pt,
// // this is not working
// it.slice(0, it.body.position(":"))
// )
}
Problem is, it.body is no string. This is not surprising, but I have a hard time to find information about the type of this expression. Could someone point me to the documentation part (searching for ‘body’ etc. didn’t return something useful in this context)? Also, exists some conversion functionality for the body ‘object’ returning the string in question from it?
I had found the content type yet, problem was rather its opacity. I will try the function snippet, thank you for it.
I agree with the closed github issue, this kind of (or general) string casting is a candidate for the public interface. Yet my case - creating additional title pages for the parts of a book - is a relatively common pattern.
As I understand it an official way to convert content to string will never happen since content is intentionally opaque. Here’s a way to achieve a similar result without having to convert to string (or break at :).
I remembered the name-it package exists which takes whole numbers and converts them to their English equivalent (1->“one”). This reduces the difference between the “convert to string and split” and “separate numbering from it.body” methods. I guess there would still be differences in references and an outline. If this becomes a problem for you please specify how it should behave.
Note that the titleize package could also be used for converting one → One. It would actually be the better choice if you have more than 20 level one headings since my small function would not be correct for anything with two words like “twenty one”.
It is a bit more complicated, but the idea of utilizing the numbering might be helpful. My case is the automatic translation of a PDF in English into German. The book was read by a combination of LLM’s and classic python code (pymupdf), the translated content returned as JSON. This way, “Part One: Fundamentals” becomes something like “Teil Eins: Grundlagen”.
The complete typst code will be generated from said Python project and a resulting PDF will be created from it. The part string will serve as a level 1 header and is re-used in different places, the mentioned title page, where its broken-up components finally have to be at exact positions of background images (semantics and length counts here: 1 or “Eins”) for every part, also as slightly changed header text together with the chapter names on every page for all the chapters of the respective part and also at some other places.
Not quite sure yet about my final approach, I’m still testing reasonable general approachs of transforming the header. For this, type-safe access to all his building blocks - strings one of them - would be desirable.
Is it important that the heading (“Part One: Fundamentals”) be the same element as the title page (“Fundamentals”)? If not, you could define your own function that places the heading, then places the title page:
#import "@preview/name-it:0.1.0": name-it
#let title-case(word) = {
upper(word.slice(0, 1))
word.slice(1)
}
#let make-title(body) = {
set align(center + horizon)
set text(size: 30pt)
body
}
#let heading-and-title(body) = {
pagebreak(weak: true)
set align(center)
set text(size: 12pt, weight: "regular")
heading(body, numbering: (params) => "Part " + title-case(name-it(params)) + ": ")
make-title(body)
}
#outline()
#heading-and-title[Fundamentals]
#lorem(50)
== More details
#lorem(50)
#heading-and-title[Other]
#lorem(50)
This way when other parts of your document go looking for level one headings it only finds what was created by the call to heading(...) in heading-and-title().
As for counting in German, I don’t know of any Typst Universe package that does that. If you can generate the “Eins”, “Zwei”, etc… in your Python script, then you could modify heading-and-title() to accept another parameter then insert it into the numbering:
#let heading-and-title(part-num, body) = {
pagebreak(weak: true)
set align(center)
set text(size: 12pt, weight: "regular")
heading(body, numbering: (params) => "Part " + part-num + ":")
make-title(body)
}
//Call it like this:
#heading-and-title[Zwei][Würst]
… that’s something, but for content, type() is not enough. The next level are func() and fields():
#show heading: it => {
[#it.body.func()] // text
[#it.body.fields()] // (text: "Example")
}
You see that the heading body (in this case) is text, with the single field text (all the other fields are not actually part of the text element; that’s a specialty of the text element).
With that knowledge, you can inspect content, and construct snippets such as the “turning content into string” one.
After two days, I’m in no way close to have a grasp for even the syntax yet :) I tried type, but as type(it.body), not [#type(it.body)]. This returned an error, and I continued elsewhere. Mind me.
The pointer to the introspection methods is very appreciated! Guilty by ignorance, I have very likely to absorb more of the fundamentals as the next step. But so far, I like the technology and the results.
This is a situation where panic(type(...)) is also useful, because you can always panic.
The problem is that type() returns a value of type type, not content. Let’s say you try to “print” this (more accurately: emit it as content to your document) by putting it in a code block, and that code block contains some other content:
#{
[abc]
type("")
}
This means that the two values are “joined”; it’s equivalent to [abc] + type(""). But joining is only supported for certain (combinations of) types: content, content and str, arrays, … but not content and type!
Therefore you need to put this into a content block: [abc] + [#type("")]. For repr() this wasn’t necessary, since it returns a str, which can be joined with content without issue.
No. Because of context behavior, panicing in context doesn’t always work. I faced this issue many times, and it’s really annoying, since panic is basically the only tool Typst provide for debugging anything.