How can I produce two outputs from one typ file, one with the full document and the other with only a subset of selected figures with caption?

Hey,

I’d like to produce two outputs from one typst file. I’m a teacher, one would be the full document that I’d share online with the students. The other would only contain the figures that the students need to draw on. This output is meant to be printed.

I’d like to be able to produce the two outputs from the same typst file with a command line input argument, like shown in this post : Can I configure my document (e.g. draft/release version, color theme) when creating a PDF without modifying the Typst file directly?
Figures which have the to-print argument set to true would show-up on this output, while everything else would not show up (except maybe the title, headers and footers). The figure number in the caption need to be the same on both outputs.

To achieve this, I need to make a show rule to show nothing but the figures with the to-print argument set to true. Is that doable ?

A non-working minimal example of what I’m trying to achieve would look like this. I tried to load the figures I want to print in a state array, and to call them if compiled with an input cli argument. However the show: none rule called after to negate all other content prevents the state from being updated so the output is empty. Moreover the figure number are not the same as in the full output document. Any help would be appreciated !

File main.typ

#import "mypackage.typ": config, myfig,
#import "mytemplate.typ": mytemplate, 

#let cfg = config(sys.inputs)
#show: mytemplate.with(cfg,)

// This text doesn't show up on the to-print output
Some text

// This figure doesn't go to the to-print output.
#myfig(
    rect(fill: blue, width: 100%, height: 5cm),
    caption: [A blue rectangle],
    to-print: false,
)

// This figure does.
#myfig(
    rect(fill: green, width: 100%, height: 5cm),
    caption: [A green rectangle],
    to-print: true,
)

File mytemplate.typ

#import "mypackage.typ": toprint-array,

#let mytemplate(cfg, body) = {
    if cfg.output-mode == "print" {
        for fig in toprint-array.final() {
            fig
        }
        // This doesn't work as nothing is shown, so the state array with the figures to print doesn't contain anything
        show: none
        body
    } else {
        body
    }
}

File mypackage.typ

#let config(inputs) = {
    let mode = if "output-mode" not in inputs.keys() {
        "full"
    } else {
        inputs.at("output-mode")
    }
    let cfg = (
        output-mode: mode
    )
    cfg
}

#let toprint-array = state("toprint-figs", ())
#let myfig(to-print, ..args) = {
    let fig = figure(..args)
    if to-print {
        toprint-array.update(arr => {arr.push(fig); arr})
    }
    fig
}

My first though went another direction: Any figure that may or may not get printed should be created with a function that either places a figure in the document, or steps the figure counter by one.

//A couple things just to demonstrate
#set figure(caption: [A figure])
#let dummy-image = box(width: 1cm, height: 1cm, stroke: 1pt)

//This would be created as you already are doing, but could also be a simple boolean
#let cfg = (
  output-mode: true
)

//Function that optionally includes the figure, but always steps the figure counter
#let myfig(cfg, ..args) = {
  if cfg.output-mode {
    figure(..args)
  } else {
    context counter(std.figure.where(kind: image)).step()
  }
}

#outline(target: std.figure.where(kind: image))

#figure(dummy-image)

#myfig(cfg, dummy-image)

#figure(dummy-image)

This results in either:


or, with output-mode: false:


Edit: As @DrLoiseau points out below, this does not solve the problem. That’s because I didn’t read carefully enough and misunderstood the problem :sweat_smile:

You can pair it with sys.inputs, if CLI is used.

The drawback is that I need to put cfg as an argument to the figure function, and that would be a breaking change for my previous documents, but that’s doable.

@Andrew yes the sys.inputs is already included in my example (see main.typ)

But that doesn’t solve the main problem : how not to show anything else than the figures I want to put in the printed output ?

Something like this seems to do the job, but it’s definitely a workaround to try to hide the whole rest of the document while still extracting the figures. Maybe you could try and see if it’s good enough, I feel it’s a bit flawed.

#let is-print = true
#show: doc => {
  if is-print {
    // show only figures with <print-figure> label.
    context {
      // you could use either opt-in or opt-out for figures
      let show-figure(fig) = fig.at("label", default: none) == <print-figure>
      let figures = query(selector(figure))
      for fig in figures {
        if show-figure(fig) { fig }
        else { hide(place(fig)) }  // step counter
      }
    }
    // a tricky part - we need to preserve enough of the document
    // so that we can still preserve the figures to query for them
    // but not show anything...
    show par: none  // just hide a lot for good measure..
    show heading: none
    show text: none
    show figure: none
    hide(place(doc))
  } else {
    doc
  }
}

= Heading
#lorem(10)

#figure([A], caption: [Aaaaa])
#figure([B], caption: [Bbbbb])<print-figure>
#figure([C], caption: [Ccccc])<print-figure>

Using none doesn’t remove side effects like changes to the Document Outline (Improve documentation for hide by Andrew15-5 · Pull Request #7201 · typst/typst · GitHub). Replacing show rules · Issue #7165 · typst/typst · GitHub will probably fix this.

IIUC, the impact is that hidden headings will still be in the PDF bookmarks.

1 Like

Thanks, that solves my problem, even though it is a bit clunky. It would be nice to be able to show nothing but selected elements, maybe this will come with custom elements ?

I adjusted your code to match a marker I put in the label of figures I want to show in the printed output. That allows me to use labels and references for my figures.
It looks like this

let show-figure(fig) = "_print_" in str(fig.at("label", default: none))
1 Like

Use typc for code mode code blocks.

If this becomes an issue, then you can turn off bookmarks in the hide version of the document.