Text along/on a path
The killer feature of tikz is being able to shape text to the path, so if the path is a wave, then the text will also be wavy. With Typst 0.10.0 this text also should be able to be filled with colorful gradient to achieve an absolute masterpiece!
This will greatly increase creativity/beautifulness of some custom papers like brochures, booklets, invitation/congratulation cards etc.
https://tikz.dev/tikz-decorations https://tex.stackexchange.com/a/640598 https://tex.stackexchange.com/a/22316 https://latexdraw.com/how-to-write-a-text-along-path-using-tikz-speedometer-case/
This isn't easily possible without extra support from the Typst compiler. You could try and break up content but it would be tricky to rotate each piece correctly while keeping styling correct and would break very easily.
While it's not possible to split content at the moment, it is possible to split strings. Here's a minimal working example:
#import "@preview/cetz:0.2.2"
#context cetz.canvas({
import cetz.draw: *
let windows(arr, size) = {
array.range(arr.len() - size + 1).map(i => {
arr.slice(i, count: size)
})
}
let text-along-path(text, path, start-percentage: 0%, end-percentage: 100%) = {
let letters = text.clusters().map(cluster => [#cluster])
let widths = letters.map(letter => {
let width = measure(letter).width
if width == 0pt {
// Measuring a single space returns a width of 0pt.
// This is a hack to get the width of a space.
measure([X X]).width - measure([XX]).width
} else {
width
}
})
let total_width = widths.sum()
let total_percentage = end-percentage - start-percentage
let distance_covered = 0
let anchors = ()
let anchors = for w in widths {
let relative_width = w / total_width
let percentage = start-percentage + total_percentage * distance_covered
distance_covered += relative_width
(percentage,)
}
anchors.push(end-percentage)
for ((percentage_1, percentage_2), letter) in windows(anchors, 2).zip(letters) {
let midpoint = (percentage_1 + percentage_2) / 2
get-ctx(ctx => {
let (ctx, percentage_1, percentage_2) = cetz.coordinate.resolve(
ctx,
(name: path, anchor: percentage_1),
(name: path, anchor: percentage_2),
)
let angle = cetz.vector.angle2(percentage_1, percentage_2)
content(
(name: path, anchor: midpoint),
anchor: "south",
angle: angle,
letter
)
})
}
}
bezier(name: "line", (0, 0), (2, 0), (1, 1), stroke: 0.2mm + blue)
let text = "Hello, World!"
text-along-path(text, "line", start-percentage: 10%, end-percentage: 90%)
})
It should also be possible to determine start and end percentage based on the measured total width and the total length of the path, but I haven't implemented this yet.
Nice solution, @rmburg! Thanks 🙂
Some things seem to have changed since cetz:0.2.2. If you change the first line to cetz:0.3.4 so it runs on Typst 0.13, the height of the comma is not calculated from the proper baseline (the bottom of the thick part) but from the bottom part of the whole glyph (the end of the tail). While this is relatively minor with a comma, it is catastrophic with letters descender, like p and g.
Change anchor: "south", to anchor: "base", to solve this.
How do you change the text attributes of the letters (font, size, fill, etc…)? Changing letter within the context command to text(fill: red, letter), for example, doesn’t work. I must have missed something trivial…
in this case, you have to use std.text, because one argument of the function is named text. Alternatively, rename the argument.
Oh my… I knew I must have missed something 🤦♀️ Thanks! 🙂
BTW there’s now a package for positioning text around an arc or circle, called Curvly.