How do I compile parts of my document with one configuration, while the overall document has a different one?

Hi everyone!

I probably should’ve asked earlier about the overall design of it before trying to implement it for ~5 days xD
Anyways, I give up

Let me explain what I want with pictures (code is below ofc)

Assume we have this project structure:

.
├── preamble.typ
├── lecture-notes.typ
└── notes/
    ├── lecture-1.typ
    └── lecture-2.typ

I basically want to have each lecture stored in a separate / compilable file. Here’s what the ideal lecture-1.pdf would look like:

Notice that:

1. In the cover page, we include
- lecture number
- to what course it belongs,
- last edited time of that particular `lecture-1.typ`
- student and instructor's names and emails
  1. Now, the trickiest part I’ve been struggling with – numbering:
    • (before jumping to answer though read to the end, please – it’s not that simple)
    • All theorem boxes and equations must use global numbering
    • I.e. instead of Theorem 1.5.4 and Equation 2.1.7 we should have Theorem 14 or Equation (vii)

Now, without copy-paste and so on, I’d like lecture-notes.typ to be almost as simple as:

// ...
#include "notes/lecture-1.typ"
#include "notes/lecture-2.typ"

That will render like this:

Here, observe that:

1. The cover page contains almost the same information:
- now we display the course name, instead of lecture number
- a brief description underneath
- last edited time of `lecture-notes.typ`
- names and emails
- **new**: outline
  1. Each individual lecture now becomes a heading and is listed in the outline
  2. And again – the trickiest part. Numbering changes:
    • Theorem boxes and equations use the numbering of the heading above them as a base.
    • So we get stuff like Theorem 1.2 and Equation 1.5.7.

I will also mention for the record that I have those things in my preamble, just in case it might be relevant:

All links are underlined with:
#show link: it => {
    underline(stroke: (dash: "densely-dotted"), text(fill: blue, it)) 
}
Only labeled equations are numbered:
// Pay attention to 
//  /* THE CORRECT NUMBERING DEPENDING ON CONTEXT */ 
//  comments below

#show math.equation: it => {
    if it.has("label") {
        math.equation(
        block: true, 
        numbering: /* THE CORRECT NUMBERING DEPENDING ON CONTEXT */, 
        it)
    } else {
        it
    }
}

// after applying the function above referencing needs to be fixed:
#show ref: it => {
    let el = it.element
    let sup = if it.has("supplement") and it.supplement != auto {
        it.supplement + " "
    } else {
        ""
    }
    if el != none and el.func() == math.equation {
        link(el.location(), sup + numbering(
            /* THE CORRECT NUMBERING DEPENDING ON CONTEXT */,
            counter(math.equation).at(el.location()).at(0) + 1
        ))
    } else {
        it
    }
}
Theorem boxes are created like this:
// Pay attention to 
//  1. the comment inside the definition of the below function....
//  2. /* DEPENDS ON CONTEXT */ comments even further below

// ....It explains how we should change ctheorems.thmbox() params, 
//  depending on whether it's a subfile (i.e. `notes/lecture-1.typ`) or the root file (`lecture-notes.typ`).

#import "@preview/ctheorems:1.1.0": *

/// Returns a theorem box
/// - identifier (str): is just the name of that box's counter.
/// - head (str): that actual string we see in the rendered box itself, like "Theorem" or "Lemma" 
/// - side_color (color, auto): if auto, it is derived from `color` argument.
/// - supplement (str, auto): supplement for references, defaults to `head`
/// - base (str, none): base counter name, can be "heading" or none
/// - base_level: (int, none): number of base levels to use
#let mytheorem(
    identifier, 
    head, 
    color, 

    side_color: auto,
    supplement: auto,
    base: auto,
    base_level: auto,
) = {

    // 1. If we are in the root file, i.e. `lecture-notes.typ`, we need to set:
    //     - base = "heading"
    //     - base_level = none
    //     - note that `identifier` (i.e. the name of the counter) is the same for all theorem boxes
    //        so that we count as «Theorem 1», «Remark 2»
    // 2. If we are inside a subfile, i.e. `notes/lecture-1.typ`, we need to set:
    //     - base = none
    //     - base_level = 1
    //     - we need to set `identifier` to a unique one for each theorem box. 
    //       so that we cound as «Theorem 1», «Lemma 1», «Theorem 2», «Lemma 2», «Remark 1»

    if supplement == auto {
        supplement = head
    }
    if side_color == auto {
        side_color = color.darken(20%).saturate(50%)
    }

    // see https://typst.app/universe/package/ctheorems/ docs
    return thmbox(
        identifier,
        head,

        fill: color, 
        stroke: (left: 0.21em + side_color),
        padding: (top: 0pt, bottom: 0pt),
        radius: 0pt,
        breakable: true,

        supplement: supplement,
        namefmt: x => [(#x)],
        titlefmt: x => [#smallcaps(text(x, weight: "extrabold"))],
        bodyfmt: x => [#x],
        base: base,
        base_level: base_level,
    )
}

#let theorem = mytheorem(
    /* DEPENDS ON CONTEXT */
    "Theorem", 
    rgb("#edf7ff"),

    base: /* DEPENDS ON CONTEXT */,
    base_level: /* DEPENDS ON CONTEXT*/
),

#let lemma = mytheorem(
    /* DEPENDS ON CONTEXT */
    "Lemma", 
    rgb("#efe6ff").lighten(50%),

    base: /* DEPENDS ON CONTEXT */,
    base_level: /* DEPENDS ON CONTEXT*/
),

What I’ve tried:

  1. Just blindly applying (different) preambles both in notes/lecture-1.typ and lecture-notes.typ doesn’t work at all
    • This is because in the lecture-notes.typ, after the first #include "notes/lecture-1.typ, our styling and numbering become like those intended to be used during separate-lecture compilation.
    • I.e. lecture-notes.pdf becomes almost like note/lecture-1.pdf
  2. Store state("location") as either root or sub, depending on the file type. And updating that accordingly.
    • That works for other functions with simple logic like if (inside here) {static content} else {another static content}
    • With many-many attempts didn’t manage to make it work for the preamble function, that has some set and show rules etc
    • And the hardest I guess it to make it work for the theorem boxes…

End :face_holding_back_tears:

Absolutely any piece of advice or suggestion on how to implement that are very much appreciated! I’m a beginner in typst tho, so please, try to make it as concrete as possible :innocent:

Or maybe there are already some templates/packages available that do that or something similar, that you might direct me to

Thanks in advance!

1 Like

I don’t have a complete solution but a few suggestions:

  • one central issue here is loading the correct preamble depending on the document type. I think the easiest (and maybe only?) way to do this is by passing some info from outside. See Can I configure my document (e.g. draft/release version, color theme) when creating a PDF without modifying the Typst file directly? - #3 by SillyFreak how to do this. I’d suggest passing --input lecture-notes=true for lecture-notes.typ and defaulting to false otherwise. You lecture preamble would then check whether that is set, and operate as a no-op if it is.
  • to have a show: preamble be a no-op you can use the following pattern:
    #show: it => {
      if lecture-notes {
        show: preamble
        it
      } else {
        it
      }
    }
    
    (I would actually put this trick into the preamble but you get the idea I hope)

I think I remember that @Goossa(?) had a project that required something like this, so maybe they also have some input. It’s been a while ago, it may predate the whole --input feature…

It shouldn’t be that messy. If you add settings in the .vscode/settings.json configuration file as

  "tinymist.typstExtraArgs": [
    "--input",
    "arg=val",
    // etc.
  ],

that would work out. If you have multiple folders with differents kwargs, then use a multi-root workspace.

I don’t think this will answer the question much, but as an example of what I had to deal with when bundling together sub-documents: Entry point (main file) detection within Typst document for nested projects · Issue #4502 · typst/typst · GitHub.