Universe

Typst symbols can currently only be defined with single-codepoint strings as variants. This package provides a workaround that allows defining symbols with anything as variants.

This works by using the Supplementary Private Use Area-B, which contains 65534 Unicode code points that don’t have a predefined meaning.[^1]

[^1]: There are also other Private Use Areas that could have been used, but this package only uses the last one since it is the least likely to already be used for a different purpose in the same document.

How to use

Start by importing

#import "@preview/symbolx:0.1.0": symbol, symbolx-rule

Then you can define your first symbol using the symbol function, which is a drop-in replacement for the std.symbol constructor.[^2] If all variants are already single-codepoint strings, then symbol works exactly like std.symbol. Otherwise, it will return a pair of (map, s), where s is the symbol and map is a special value that contains a map from Private Use codepoints to non-single-codepoint-string variants.

[^2]: Do note that this will shadow the symbol type, so e.g. to compare type(x) == symbol, you’d have to write type(x) == std.symbol, since symbol is now a function and not a type, so the former comparison would always be false.

Then, you can pass that map to the symbolx-rule function, which will return a show rule that makes the whole thing work. For example:

#let (map, tensor) = symbol("⊗", ("r", $times.circle_RR$), ("c", $times.circle_CC$))
#show: symbolx-rule(map)

$ (CC^2)^(tensor n) = (CC^2)^(tensor.c n) != (CC^2)^(tensor.r n) $

If you want to define more than one symbol with non-single-codepoint-string variants, you have to pass the map returned from the first symbol call as the first positional argument to the second symbol call:

#let (map, tensor) = symbol("⊗", ("r", $times.circle_RR$), ("c", $times.circle_CC$))
#let (map, subset) = symbol(map, "⊂", ("c", $class("binary", subset subset)$))
#show: symbolx-rule(map)

$ A subset.c B $