badlyenginee.red

Some Cool Typst Things

2026-09-02


So playing with Typst I’ve found some cool things we can do. I have a couple of examples that are probably obvious to people who know Typst well, but it was fun figuring some of these things out for different use cases I’ve encountered already.

SVG Manipulation

Reading an SVG is pretty simple. You call #image#image and pass the path which will render the image. For example calling #image("hotairballoon.svg")#image("hotairballoon.svg") will show this hot air balloon SVG that I pulled from here:

But let’s suppose that your business doesn’t use this color scheme and you’d rather have the balloon follow your branding guides. Normally, you would have to open the SVG in an image editor that can handle SVGs, modify the colors, save it as a new file, and then use it in your document. One thing you can do with Typst is just replace the contents in the SVG.

Let’s write a function to replace the colors:


Color Replacer

#let recolor(path, colors: ()) = { [1]
let svg = read(path) [2]
for (old, new) in colors {
svg = svg.replace(old, new) [3]
}
bytes(svg) [4]
}
#let balloon = recolor( [5]
"hotairballoon.svg",
colors: (
"#5546CB": blue.to-hex(),
"#FDCD60": red.to-hex(),
"#FF8859": green.to-hex(),
)
)
#image(
height: 9em,
balloon,
)
#let recolor(path, colors: ()) = { [1]
let svg = read(path) [2]
for (old, new) in colors {
svg = svg.replace(old, new) [3]
}
bytes(svg) [4]
}
#let balloon = recolor( [5]
"hotairballoon.svg",
colors: (
"#5546CB": blue.to-hex(),
"#FDCD60": red.to-hex(),
"#FF8859": green.to-hex(),
)
)
#image(
height: 9em,
balloon,
)
  1. Take in the path to the file and a dictionary of colors
  2. Read the SVG in as a string
  3. For each color, swap out the old with the new
  4. Read the SVG as bytes which is what #image#image can use
  5. Call recolorrecolor with our new data


We could inline the image output directly in the recolor function, but this would require us to take options for outputting the image. If we simply return the bytes at the end, then we can use this as a variable to #image#image itself and then just pass our options to #image#image.

This gives us the same image with the colors swapped:

Now this would be tedious if we have a ton of colors that we need to replace, but it does work for simpler logos and images.

Drafts

Let’s suppose that we are generating documents that we need to hand out. We want to get approval on these before we actually send them and need to make it clear that these are a draft. With Typst, we can generate a foreground that will automatically add “DRAFT” to our pages.

Assume we have exported our own theme like this:


Theme

#let theme(body) = {
set text(font: "PT Sans")
set page(
paper: "us-letter",
margin: (
top: 1.2in,
left: 2in,
right: 2in,
bottom: 0.8in,
),
)
show title: set align(center)
show title: set text(size: 1.9em)
show link: underline
show link: set text(blue)
body
}
#let theme(body) = {
set text(font: "PT Sans")
set page(
paper: "us-letter",
margin: (
top: 1.2in,
left: 2in,
right: 2in,
bottom: 0.8in,
),
)
show title: set align(center)
show title: set text(size: 1.9em)
show link: underline
show link: set text(blue)
body
}

This will give us a simple PDF that is standard US letter size, using PT Sans font and some default margins. We also set some styling for our title and our links. But if we make a couple of modifications, we can add our draft status to our theme.


Theme

#let theme(draft: false, body) = { [1]
set text(font: "PT Sans")
set page(
paper: "us-letter",
margin: (
top: 1.2in,
left: 2in,
right: 2in,
bottom: 0.8in,
),
foreground: if draft { [2]
place( [3]
center+horizon,
text(
size: 22em,
fill: gray.opacify(-80%), [4]
rotate(45deg)[ [5]
*DRAFT*
]
)
)
}
)
show title: set align(center)
show title: set text(size: 1.9em)
show link: underline
show link: set text(blue)
body
}
#let theme(draft: false, body) = { [1]
set text(font: "PT Sans")
set page(
paper: "us-letter",
margin: (
top: 1.2in,
left: 2in,
right: 2in,
bottom: 0.8in,
),
foreground: if draft { [2]
place( [3]
center+horizon,
text(
size: 22em,
fill: gray.opacify(-80%), [4]
rotate(45deg)[ [5]
*DRAFT*
]
)
)
}
)
show title: set align(center)
show title: set text(size: 1.9em)
show link: underline
show link: set text(blue)
body
}
  1. Add draft argument to our theme
  2. If draft is set to true, set the foreground
  3. Place will add our element on the page separate from the rest of the content
  4. Set our color, with 20% opacity
  5. Rotate our text by 45 degrees


Now if we call our theme with draft set to true we should see something like this with the “DRAFT” overlay on every page.


#show: theme.with(draft: true)
= Hello
#lorem(100)
#show: theme.with(draft: true)
= Hello
#lorem(100)