I would like to use Typst to create small images for a eink screen I have. I am thinking using Rust templating strings to create the Typst code and then compiling it to PNG then BMP.
I have had a play with typst-as-lib but it seems a bit overkill and can’t seem to get it to work.
I just need to pass in a Typst string into the compiler, load system fonts and then spit out a png buffer, any help would be much appreciated!
A better approach would be to write a Typst template, and then populate it using Typst’s data loading capabilities. See e.g. here for a discussion (including embedding this in a Python script.
If you have specific questions, we can try to help. But it might be easier to call the Typst compiler executable instead of the library, depending on what the environment you’re running in is like.
Yeah, from what I’ve seen online a lot of setup is required to use Typst as a library and actually didn’t think about calling the binary itself directly - so I’ll try that.
I am going to be running in a Raspberry Pi Zero 2 W, so it shouldn’t be too much of a constraint.
I was originally planning to use HTML and then call Chromium functions to render the webpage as an image, but that was going to be a little too extreme for the Pi and I have more experience with Latex anyway.
I saw that as well! That’s what inspired me to check out Typst, and I must say I’m loving it so far.
If anyone is wondering how to do this, I just called the Typst binary with the Rust code passing in the template as stdin and then handling the result on stdout — it could probably be optimised more – but it works:
use std::io::{self, Write};
use std::process::{Command, Stdio};
use std::thread;
fn main() -> io::Result<()> {
let typst_string = r#"
= Hello from Rust! 🦀
Random equation,
$ attach(
Pi, t: alpha, b: beta,
tl: 1, tr: 2+3, bl: 4+5, br: 6,
) $
"#
.to_string();
let mut cmd = Command::new("typst")
.args(["compile", "-", "-", "--format", "png"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let mut stdin = cmd.stdin.take().expect("Failed to open stdin");
thread::spawn(move || {
stdin
.write_all(typst_string.as_bytes())
.expect("Failed to write to stdin");
});
let output = cmd.wait_with_output()?;
if output.status.success() {
std::fs::write("output.png", &output.stdout)?;
} else {
let error_message = String::from_utf8_lossy(&output.stderr);
}
Ok(())
}
I’m going to have a play with Typst templates now :)
Not sure whether to leave this question in here – or move it over to a different topic, but here it is.
With my eink display it is a 7 colour display, white, black and then 5 other main colours.
Is it possible when I export my Typst page into two separate layers?
One will be all the text and solid colour symbols, which will be converted to raw B&W. While the other images layer (pngs/svgs) would be passed through a dithering algorithm.
hmm, you can use data loading to configure two different outputs, and then selectively hide different parts of your output. That would probably look something like this:
#show: rest => {
let mode = sys.inputs.at("mode", default: "all")
if mode == "text" {
rest = { show image: hide; rest }
}
if mode == "image" {
rest = { show text: hide; rest }
}
rest
}
If the content isn’t so clearly separable by a show rule, you can either “manually” select what to hide for what mode, or try to attach labels to stuff and use show <label> rules.
It should be possible - as long as you don’t need some kind of state freeze, to render it as two pages, one for each layer. Using the same pseudo-showrules as previously
#let card = {...} // your whole layout of the document goes in here
#{
show image: hide
card
}
#pagebreak(weak: true)
#{
show text: hide
card
}
With this templating, I have got it working – I just don’t know how to best pass the data in, I have 3 options:
Pass the data in through --input which would be a bit clunky as would have to a new input for every set of arguments provided
Save the data need to a input.json file or similar which Typst would read.
Export the data to the Typst dictionary format and just place it directly into the template, like #let data = (name: "Ribbit", type: "frog") which has the added benefit of being able to split up the data a bit more, i.e., can have temp as one and equation as another.
Note that you can pass a string containing JSON data with --input and then work with that:
#let raw-data = sys.inputs.at("data")
#let data = json(bytes(raw-data))
That means you can achieve everything you’d want to do with input.json also using --input. For calling Typst programmatically, the advantage is imo that you don’t need to save data to a file, which would (presumably) be deleted immediately afterwards anyway.
if you mean multiple variables, you can do the same by passing a JSON dictionary one way or the other:
#let (temp, equation) = json(...)
// where the JSON is of the form `{"temp": ..., "equation: ...}`
So I think there’s not really a tradeoff with --input vs. JSON vs. generating Typst code. Maybe that makes it easier to choose the variant you like the most
Sorry, for all the questions on the same thread and thanks again for your help :)
Would you know if there was a plan for Typst to expose/release Rust bindings for itself? To be used a library - instead of it having it to be used as a direct command.