How to cross-reference labels defined in external typst projects?

In my main.typ file, is it possible to reference labels that are defined in an external extern.typ file, especially when main.typ and extern.typ are not in the same directory?

In LaTeX, I usually use the xr package to achieve this. By adding \externaldocument[AB-]{extern} in the preamble, I can reference a label like \label{eq:1} defined in extern.tex with \ref{eq:1}. And all references from extern.tex are automatically prefixed with AB-.

Is there a similar way to handle this in Typst?

2 Likes

Yes.

No, if extern.typ is outside of the root directory (typst c --root), then this is not possible.

IIUC, LaTeX can access arbitrary file when reading or writing to a file system. This is a potential hazard for user privacy, as any piece of code in LaTeX (any package) can read potentially sensitive data and include it in the result document without the end user noticing this. Later, by accessing the document (which might be publically published by the user), a bad guy (maybe the author of the package) can read that sensitive data and use it for their advantage.

This is something that Typst can’t risk happening, which is why it doesn’t allow random access to anything, only to download packages marked as #import "@preview/package:0.0.1". You can’t write anything to the file system and read outside of the root directory. There is no access to the network or time (except datetime.today()), same goes to the Wasm plugins.

If files are in the root directory, then you can do:

$ x = y $ <eq:1>
#show math.equation.where(block: true): set math.equation(numbering: "1.")
#include "extern.typ"
@eq:1

Or:

#let section = [
  $ x = y $ <eq:1>
]
#show math.equation.where(block: true): set math.equation(numbering: "1.")
#import "extern.typ": section
#section
@eq:1

I guess you can also include an external file, but it has to be a package in the designated package directory. But that doesn’t make much sense, as the content in the package isn’t very flexible (it’s hard-coded).

As a last resort approach, you can use symbolic/hard links (if they are supported by the file system).

I think this can be done (at least in simple cases) but you need to run typst query on the external document before you compile the main document. Here’s a proof of concept:

external.typ

#set heading(numbering: "1.")
#set math.equation(numbering: "(1)")

= First section <sec:1>
$ x $ <eq:x>

= Second section <sec:2>
== First subsection <sec:sub1>
== Second subsection <sec:sub2>
$ y $ <eq:y>

To access these external refs in our main file, we extract them with typst query:

typst query external.typ 'selector(heading).or(math.equation)' > refs.json

Then we can import them like this:

main.typ

// Increment a counter array
#let increment(cnt, level) = {
  if cnt.len() < level {
    cnt = cnt + (0,) * (level - cnt.len())
  }
  cnt.at(level - 1) += 1
  return cnt
}

// Make label-name -> element-number dict for elements in given JSON file
// This could be extended to handle supplements automatically
#let get-refs(filename) = {
  let data = json(filename)
  let refs = (:)
  let counts = (:)
  for x in data.filter(x => x.numbering != none) {
    let count = counts.at(x.func, default: (0,))
    count = increment(count, x.at("level", default: 1))
    if x.label != none {
      refs.insert(
        x.label.slice(1, -1),
        numbering(x.numbering, ..count).trim(regex("[().]")),
      )
    }
    counts.insert(x.func, count)
  }
  return refs
}

// Load external referendces
#let external = get-refs("refs.json")

#show ref: it => {
  let ext = external.at(str(it.target), default: none)
  if ext != none {
    return [ext-#ext]
  }
  return it
}

In Equation~@eq:x of Section~@sec:sub1 we see....

image

This probably breaks in many ways, but it shows that external refs should be doable, maybe not in the general case but at least for a particular document template.

Your solution emits the content of extern.typ into the current document and any contextual attributes are taken from the position where the content appears.

OP might desire to have two independent documents extern.pdf and main.pdf with references in main.pdf referring to elements within extern.pdf.