The Julia to Typst Interface
This guide illustrates how to implement Typst formatting for custom types.
Setup
julia> import Base: show
julia> import Typstry: context, show_typst
julia> using Typstry
Implementation
Consider this custom type.
julia> struct Reciprocal{N <: Number}
n::N
end
Implement a show_typst
method to specify its Typst formatting. Remember to Annotate values taken from untyped locations.
julia> show_typst(io, r::Reciprocal) =
if io[:mode]::Mode == markup
print(io, "#let reciprocal(n) = \$1 / #n\$")
else
print(io, "reciprocal(")
show(io, MIME"text/typst"(), Typst(round(r.n; digits = io[:digits]::Int)))
print(io, ")")
end;
Although custom formatting may be handled in show_typst
with get(io, key, default)
, this may be repetitive when specifying defaults for multiple methods. There is also no way to tell if the value has been specified by the user or if it is a default. Instead, implement a custom context
which overrides default, but not user specifications.
julia> context(::Reciprocal) = Dict(:digits => 2);
Those two methods are a complete implementation of the Julia to Typst interface. The following method is optional:
julia> show(io::IO, m::MIME"text/typst", r::Reciprocal) = show(io, m, Typst(r));
Now, a Reciprocal
is fully supported by Typstry.jl.
julia> r = Reciprocal(π);
julia> println(TypstString(r))
#let reciprocal(n) = $1 / #n$
julia> println(TypstString(r; mode = math))
reciprocal(3.14)
julia> println(TypstString(r; mode = math, digits = 4))
reciprocal(3.1416)
Guidelines
While implementing the interface only requires two methods, it may be more challenging to determine how a Julia value should be represented in a Typst source file and its corresponding compiled document. Julia and Typst are distinct languages that differ in both syntax and semantics, so there may be multiple meaningful formats to choose from.
Make the obvious choice, if available
- There is a clear correspondence between these Julia and Typst values
julia> println(TypstString(1))
$1$
julia> println(TypstString(nothing))
#none
julia> println(TypstString(r"[a-z]"))
#regex("[a-z]")
Choose the most semantically rich representation
- This may vary across
Mode
s and domains- Some modes may not have a meaningful representation, and should be formatted into a mode that does
- Both Julia and Typst support Unicode characters, except unknown variables in Typst's
code
mode
julia> println(TypstString(π; mode = code))
3.141592653589793
julia> println(TypstString(π; mode = math))
π
julia> println(TypstString(π; mode = markup))
$π$
Consider both the Typst source text and compiled document formatting
- A
Docs.Text
is documented to "render [its value] as plain text", and therefore corresponds to text in a rendered Typst document - A
TypstString
represents Typst source text, and is printed directly
julia> println(TypstString(text"[\"a\"]"))
#"[\"a\"]"
julia> println(TypstString(typst"[\"a\"]"))
["a"]
Try to generate valid Typst source text
- A
TypstString
represents Typst source text, which may be invalid - A
UnitRange{Int}
is formatted differently for eachMode
, but is always valid
julia> println(TypstString(1:4; mode = code))
range(1, 5)
julia> println(TypstString(1:4; mode = math))
vec(
1, 2, 3, 4
)
julia> println(TypstString(1:4; mode = markup))
$vec(
1, 2, 3, 4
)$
Test for edge cases
1 / 2
may be ambiguous incode
andmath
mode expressions$1 / 2$
is not ambiguous inmarkup
mode
julia> println(TypstString(1 // 2; mode = code))
(1 / 2)
julia> println(TypstString(1 // 2; mode = math))
(1 / 2)
julia> println(TypstString(1 // 2; mode = markup))
$1 / 2$
Format values in containers using show
with the text/typst
MIME type
- Values may require their
context
- The
AbstractVector
method- Encloses source text in dollar signs, so it changes its
Mode
tomath
- Formats its elements with an indent, so it increments its
depth
- Encloses source text in dollar signs, so it changes its
julia> println(TypstString([true, Any[1, 1.2]]))
$vec(
"true", vec(
1, 1.2
)
)$
Check parametric and abstract types
- Related Julia types may not be representable in the same Typst format
julia> println(TypstString(0:2:6; mode = code))
range(0, 7, step: 2)
julia> println(TypstString(0:2.0:6; mode = code))
(0.0, 2.0, 4.0, 6.0)