As part of a personal project, I’m trying to find a way to force line breaks in specific sections of text without needing to manually add an extra space between lines.
For example, this input:
line1
line2
line3
Should give
line1
line2
line3
And not
line1 line2 line3
I believe I might need to look into show rules using the par object, but I’m not entirely sure. Since I only want this formatting to apply in specific cases (e.g., not for headings or certain text segments), I’m also considering using regex with custom delimiters like "" or something similar.
For example with the regex :
// pseudocode that shouldn't compile
#show regex("\"\"(.*?)\"\"") : it => {
for line in it {
line + linebreak()
}
}
Any advice would be much appreciated—thanks in advance!
EDIT 1 : The objective of the template is to set up a student songbook with a lot of adjoinging lines such as I’d like to avoid people specifying a line break with \
Sorry, I wasn’t very clear in my initial message. I’m in the process of developing a template that would ensure that the \ must not be used.
This may seem like a strange comment, but as my template allows me to make a songbook containing a lot of adjoining lines, I’d like to avoid people specifying a line break
Yeah, that is not what I’ve read. The title is also not what you are describing. I think you want “How to force line breaks between adjacent lines without explicit linebreak?”
Do you use any special markup syntax at all? You can easily use raw element for this purpose:
#show raw: set text(11pt, font: "Libertinus Serif")
// #show raw: set par(leading: 0.65em)
```
line1
line2
line3
```
If your lines are not a one continuous text element, then it’s impossible to detect a soft line break, as it’s just a space.
#show: it => for child in it.children {
if child.func() == [ ].func() { linebreak() } else { child }
}
#lorem(2)
#lorem(2)
#lorem(2)
word --- line1
word --- line2
word --- line3
This is just like raw element workaround, but with extra steps and not much better.
Actually, you can just use #show [ ].func(): linebreak(), but either way, it will replace any standalone space element with a linebreak. This doesn’t feel robust.
An actual solution would be to not rely on plain markup to distinguish between separate adjacent lines. Pass all lines as separate arguments to a function. Or, I guess you can exploit the list element, like I did in the past:
#let hack(body) = {
set list(marker: none, body-indent: 0pt)
body
}
#hack[
- word --- line1
- word --- line2
- word --- line3
]
As far as I know, raw is the only element in typst that allows you to preserve whitespace and linebreaks (it comes with some drawbacks - it’s a verbatim environment, can’t apply any other styling to the text, typst markup is not valid inside raw blocks).
But raw is has one configurability, it’s parametrized by “language type” and we can use that to style song lyrics specifically for example like this…
#show raw.where(lang: "lyrics"): set text(font: "Libertinus Sans")
```lyrics
Blackbird singing in the dead of night
Take these broken wings and learn to fly
All your life
You were only waiting for this moment to arise
Blackbird singing in the dead of night
Take these sunken eyes and learn to see
All your life
You were only waiting for this moment to be free
Blackbird, fly
Blackbird, fly
Into the light of the dark black night
```
If the book is only lyrics, then maybe the marker lyrics is not necessary either.
And there are packages that style code and raw blocks - which means it can be done similarly for songs as well, I think.
I think it’s a viable option and a good compromise between ease and performance for a project that’s likely to include ~600 pages. I’ll come back to the blogpost when I have more convincing results.
Both solutions require a similar amount of special characters to define a verse. However, the second method is better when using additional formatting inside of the delimiter which should not be concidered as raw . Finally, the second method is logically more resource-intensive and should be tested further for performance at scale.
Note that if you have markup that breaks up your text, like ab _cd_ then regex cannot/will not match across the different styles, it can only test and match on ab and cd separately.
I’m skeptical of the regex rules in general, they are really quite limited.
Apparently I continued hacking on this: here’s how we can really extend the raw block to support more markup. We use a show rule to evaluate markup on each line. Note the limitation - it evaluates one line at a time. And like you said, every solution might need performance evaluation. Eval markup for example brings back smartquotes, i.e automatic “” or «» as appropriate per language. (And, markup eval also removes the space-preserving property of raw blocks).
So this becomes a relatively nice way to typeset a poem or a song.
#let functions = (
ul: std.underline,
)
#show raw.where(lang: "poem"): set text(font: "EB Garamond")
#show raw.where(lang: "poem"): set raw(theme: none)
#show raw.where(lang: "poem"): it => {
show raw.line: it => {
// Evaluate typst markup in the raw block
// NOTE: can only see one line at a time, line-spanning markup not supported.
// Add eval's scope argument to make custom functions available
if it.number != 0 {
// preserve initial space on the line
let prefix = if it.text.starts-with(" ") {
let space-width = 0.5em
let space-prefix = it.text.position(regex("\S"))
if space-prefix != none {
h(space-width * space-prefix)
}
}
raw.line(0, it.count, it.text, prefix + eval(it.text, mode: "markup", scope: functions))
} else {
it
}
}
it
}
```poem
*Under the greenwood tree*
Under the greenwood tree #emoji.tree
Who loves to _lie with me,_
And turn his merry note
Unto the sweet bird's throat,
Come hither, come hither, come hither:
Here shall he see
No enemy
#ul[But winter and rough weather.]
Who doth ambition shun, #emoji.sloth
And loves to live i' the sun, $smash$
Seeking the food he eats,
And pleas'd with what he gets,
Come hither, come hither, come hither:
Here shall he see
No enemy
#ul[But winter and rough weather.]
```