Universe

logo

Jlyfish is a package for Julia and Typst that allows you to integrate Julia computations in your Typst document.

Static Badge GitHub License GitHub release (latest by date) GitHub Repo stars

You should use Jlyfish if you want to write a Typst document and have some of the content automatically produced by Julia code but want the source code for that within your document source. It fills a similar role as PythonTeX does for Python and LaTeX. Note that this is different from tools like Quarto where you write documents in Markdown, also integrate some Julia code, but then might use Typst only as a backend to produce the final document.

See below for a quick introduction or read the wiki for an in depth explanation.

Since Jlyfish builds a bridge between Julia and Typst, we also have to get two things running. First, install the Julia package TypstJlyfish from the general registry by executing

julia> ]

(@v1.10) pkg> add TypstJlyfish

You only have to do this once. (It is like installing and using the Pluto notebook system, if you are familiar with that.)

When you want to use Jlyfish in a Typst document (say, your-document.typ), add the following line at the top:

#import "@preview/jlyfish:0.1.0": *

Then, open a Julia REPL and run

julia> import TypstJlyfish

julia> TypstJlyfish.watch("your-document.typ")

Jlyfish facilitates the communication between Julia and Typst via a JSON file. By default, Jlyfish uses the name of your document and adds a -jlyfish.json, so your-document.typ would become your-document-jlyfish.json. This can be configured, of course.

To let Typst know of the computed data in the JSON file, add the following line to your document:

#read-julia-output(json("your-document-jlyfish.json"))

You can then place some Julia code in your Typst source using the #jl function:

What is the sum of the whole numbers from one to a hundred? #jl(`sum(1:100)`)

Head over to the wiki to learn more!

Just to show what is possible with Jlyfish:

demo

#import "@preview/jlyfish:0.1.0": *

#set page(width: auto, height: auto, margin: 1em)
#set text(font: "Alegreya Sans")
#let note = text.with(size: .7em, fill: luma(100), style: "italic")

#read-julia-output(json("demo-jlyfish.json"))
#jl-pkg("Colors", "Typstry", "Makie", "CairoMakie")

#grid(
  columns: 2,
  gutter: 1em,
  align: top,
  [
    #note[Generate Typst code in Julia:]

    #set text(size: 4em)
    #jl(```julia
      using Typstry, Colors

      parts = map([:red, :green, :purple], ["Ju", "li", "a"]) do name, text
        color = hex(Colors.JULIA_LOGO_COLORS[name])
        "#text(fill: rgb(\"$color\"))[$text]"
      end
      TypstText(join(parts))
    ```)
  ],
  [
    #note[Produce images in Julia:]

    #set image(width: 10em)
    #jl(recompute: false, ```
      using Makie, CairoMakie

      as = -2.2:.01:.7
      bs = -1.5:.01:1.5
      C = [a + b * im for a in as, b in bs]
      function mandelbrot(c)
        z = c
        i = 1
        while i < 100 && abs2(z) < 4
          z = z^2 + c
          i += 1
        end
        i
      end

      contour(as, bs, mandelbrot.(C), axis = (;aspect = DataAspect()))
    ```)
  ],
  [
    #note[Hand over raw data from Julia to Typst:]
    #let barchart(counts) = {
      set align(bottom)
      let bars = counts.map(count => rect(
        width: .3em,
        height: count * 9em,
        stroke: white,
        fill: blue,
      ))
      stack(dir: ltr, ..bars)
    }

    #jl-raw(fn: it => barchart(it.result.data), ```julia
      p = .5
      n = 40
      counts = zeros(n + 1)
      for _ in 1:10_000
        count = 0
        for _ in 1:n
          if rand() < p
            count += 1
          end
        end
        counts[count + 1] += 1
      end

      counts ./= maximum(counts)
      lo, hi = findfirst(>(1e-3), counts), findlast(>(1e-3), counts)
      counts[lo:hi]
    ```)
  ],
  [
    #note[See errors, stdout, and logs:]

    #jl(```julia
      println("Hello from stdout!")
      @info "Something to note" n p
      @warn "You should read this!"
      this_does_not_exist
    ```)
  ]
)