Trying Typst for TTRPGs
What is Typst?
Typst is a typesetting software package intended to dislodge LaTeX from its academic (particularly STEM) typesetting throne. I've been using LaTeX a lot, but around a month ago I finally decided to give Typst a spin. My experience has been positive so far, and I honestly think that whether or not it achieves its goal of acceptance by prestigious math & science journals, it's going to be a fantastic asset for self-publishing RPG hobbyists. (See here for a very detailed rundown comparing Typst to LaTeX, MS Word, and Google Docs).
Typst has good tutorials that make it easy to get the basics down, so I didn't feel a strong need to write my own. Similarly, many of the things I put in my Quick & Dirty LaTeX guide are rendered trivially easy in Typst. But I've included code examples below for a few specific tricks that showcase the power of Typst.
Typst vs. LaTeX
(Based on around a month of use.)
- For a simple document, Typst uses simple, markdown-style formatting. If you're already writing in markdown, it should be really easy to port it over to Typst for print formatting. For the most part, body text in a Typst source file (i.e. the material and code written prior to rendering as a pdf) can be written to look less cluttered than LaTeX (e.g. fewer backslashes to introduce commands and syntax 'sugar' like italicizing with
_underscores_
rather than\textit{this bullshit}
). - Typst compiles WAY faster -- nearly in real-time for smaller documents. My 20,000-entry Antarctic hexcrawl (mostly placeholders so far) made a great stress-test. XeLaTeX takes just over one minute to generate a 490-page PDF, whereas Typst can do it in about 3.5 seconds. This alone is a MASSIVE point in favor of Typst.
- Typst feels less restrictive. For example, LaTeX has weird rules about choosing font sizes. You have to specify a base size of 10, 11, or 12 pt when initializing the document and then are given a fixed selection of relative sizes to choose from. Typst just lets you specify any font size wherever you want. Same goes for page sizing, customizing section headers, customizing running headers and footers, etc. LaTeX has a bloated mess of packages for working around its weirdly restrictive document classes, whereas Typst provides most of these tools up front. (Of course, a LaTeX defender would argue that LaTeX is better at enforcing good typesetting habits).
- On the other hand, LaTeX is mature software with a huge library of useful, well-optimized packages for doing almost any esoteric typesetting task you can think of, plus a massive searchable body of knowledge on Stack Exchange. Typst isn't there yet, and in attempting to recreate my LaTeX projects I've been stymied a few times and unable to find good troubleshooting tips.
- It's much, much easier to get into scripting in Typst. If you've ever dabbled in Python, you should have no problem taking advantage of Typst's scripting features (or at least the basic ones). This probably isn't critical for ttrpgs, but I've given some examples below of cool things I've been able to do with Typst.
- Finally, some minor pain points in Typst:
- Page numbering is weirdly affected by how nearby elements are formatted, whereas LaTeX has more of a 'set it and forget it' approach. Mainly an issue if you don't set up your section headers correctly.
- So far, I haven't been able to find a good app to convert LaTeX or Markdown tables to Typst. I almost always write my tables in Obsidian and use tablesgenerator.com to convert to LaTeX.
- No ability to auto-balance columns of text, so manual column breaks are required.
My sense is that Typst is easier to learn for most basic formatting, but for advanced layouts the relative ease of the language is currently offset by the dearth of packages & forum posts. Hopefully this will shift in the future and give Typst an edge.
Useful Code
(Specifically for writing TTRPGs)
Show rules
One of my favorite Typst features is the ability to set 'show rules'. These can be used to override functions and as a sort of 'find-and-replace' for specific text strings. For example, #show emph: set text(blue)
overrides the italicize function to italicize and turn the text blue. Adding #show "Dungeon Master": smallcaps("Dungeon Master")
will apply smallcaps to every instance of the string 'Dungeon Master' in the text.
For an RPG, this has obvious uses for keyword highlighting -- instead of cluttering up the document body with formatting commands and laboriously italicizing or bolding every keyword, formatting be declared up front and easily tinkered with later. Other uses include currency symbols, color-coded NPCs or items, replacing die notation with svg images of the respective dice, etc.
Update: these can be limited to a single content block by adding them to a function definition and applying that function to a single block (h/t Window Dump on the NSR Cauldron Discord).
I'm a Dungeon Master.
#show "Dungeon Master": smallcaps("Dungeon Master")
That's nice, but I'm a Dungeon Master.
#show "confusing": emph("Confusing")
#show "text": strong("Text")
#show "information": smallcaps("Information")
#show "style": strong("Style")
#show "you": strong("You")
If you want, you can style a confusing but information-dense text à la *Errant*.
As far as I know, doing this in LaTeX requires setting up macros and manually using them in the body of your text (e.g. \newcommand{\DM}{\textsc{Dungeon Master}}
would insert smallcaps 'Dungeon Master' wherever \DM
appears in the source document.).
CSV import
I've written about doing this in LaTeX before, but I'll mention that I found it a bit easier to get fancy with Typst -- the way conditional statements are implemented makes it much easier to control how the table is loaded and rendered. Anyways, here's basic code to read in a table of B/X monsters I had on hand:
= B/X Monsters // sets a new heading for this chapter
#let bestiary = csv("../Tables/Basic_monsters.csv", row-type: dictionary)
#for monster in bestiary{
[=== #monster.Name // make the monster's name a section head
#monster.HD HD, #monster.AC AC.
Attacks: #monster.Attacks, #monster.Damage
]
}
The line starting with #let
is just declaring a variable that stores the contents of the csv as a set of dictionary arrays, with each entry named according to the table's header line. Thus, monster.HD
accesses the value in the HD column of the sheet for whatever row the loop is currently on.
dxy lists
The numbered list function in typst generates an array of variables starting by default at 1 and incrementing by 1 until the end of the array. With Typst's scripting functionality we can apply some modular arithmetic to generate an array of the combinations produced by rolling two dice. (Technically, we need only specify one die, with the other determined by the length of the list).
#[ // this open bracket sets our text in a block, ensuring that the formatting changes don't affect future lists.
#let die = 6
#set enum(numbering: (..nums) => for value in nums.pos(){
{str(calc.div-euclid(value - 1, die) + 1)}+";"+{str(calc.rem({value + (die - 1)}, die)+1)}
} )
#set text(size: 0.8em)
+ *Chitinous growths:* Armor improved one level.
+ *Anodic skin:* You can attack as with a two-handed weapon against a foe in metal armor or supply power to an item requiring anodic charge.
+ *Latent telepathy:* You can communicate telepathically.
+ *Arthropod affinity:* You exude pheromones that let you give basic commands to insects and spiders.
+ *Rapid metabolism:* You are immune to mundane poisons but consume rations at twice the usual rate.
+ *Slow metabolism:* You require rations at half the normal rate, but are weak to poisons and cold.
+ *Bioluminescent:* Your skin glows in the dark. Enemies can see you coming, but you provide light equivalent to a candle in the darkness.
+ *Chlorophyll:* Instead of eating you may absorb sunlight for an hour.
+ *Macrocephaly:* Absent other mutations, you are tall and can be mistaken for an Exultant.
+ *Extra arm:* Gain a hand slot.
+ *Hirsute:* Your body is covered in a layer of fur.
+ *Mysterious excrescence:* What does it do?
+ *Infravision:* Snake-like pits near your cheekbones can sense general contrasts in heat.
+ *Ultravision:* You can see in the ultraviolet spectrum.
+ *Extra eye:* Can sense the ETH scores of encountered creatures and NPCs.
+ *Talons:* Peril as two-handed weapon.
+ *Venomous fangs:* Bite attack poisons foes.
+ *Prehensile tail:* Treat as extra hand slot.
+ *Magnetism:* Can attract small pieces of metal, and always know the direction of Idraluna.
+ *Redundant organs:* Survive twice as long at 0 HP.
+ *Metamorphic:* You can permanently sacrifice one point of PHY to shed your skin over the course of several days and completely change your appearance.
+ *Quills:* Sharp quills cover your scalp and back, impeding your ability to wear standard armor but dealing a point of peril to anyone who melee attacks you (reach weapons excluded).
+ *Centaur legs:* You count as mounted for purposes of overland travel.
+ *Horns:* Horns or antlers protrude from your head.
+ *Reptile affinity:* You exude pheromones that let you give basic commands to reptiles.
+ *Laryngeal hypertrophy:* Booming voice can be heard at a great distance.
+ *Acidic saliva:* Dissolves a fist-sized amount of organic or soluble matter in 10 minutes.
+ *Diminution:* You are 2 feet tall. You can only wield concealable weapons and can carry half standard coin-weight, but can fit into tight spaces others can't.
+ *Tulpa:* Secondary personality.
+ *Eye stalks:* Extend far enough to snoop around corners.
+ *Gliding membranes:* Stretched between your arms and legs, immune to fall peril.
+ *Club tail:* As a two-handed mace.
+ *Deep language:* Antediluvian words come to you in the night.
+ *Antennae:* Can sense air currents and identify individual people by scent.
+ *Roll twice.*
+ *Roll thrice.*]
(Note that the #show
statement from before bolding the word 'You' is still in effect).