How to get diagnostic/warning if content has been clipped?

Hello typst users,

I’m considering typst for the follwing use case:
Given a dataset in the form of a csv file I’d like to produce a PDF with one page per record. Each field of a record is placed at a specific location on the page inside a container (box or block) element with a predefined size.
There are edge cases where the input data is long enough to overflow the container. Since the produced PDF can contain about a thousand pages, I’d like to automate the detection of overfull containers so that I don’t have to scan visually the PDF, by manually scrolling through it.
If typst could warn me about those cases I could intercept the diagnostics and tell the user on which page the problem occurs.

I’ve tried this but got no warning:

#set page(width: 10cm, height: auto)
#block(width: 10pt, height: 10pt, fill: gray, "Hello World")

Any idea if this is possible?

Hello. Your description sounds like you want to do

#let file = ```csv
record1,field1,field2,field3
record2,field1,field2,field3
```.text

#let data = csv(bytes(file))

#for record in data {
  place(block(width: 5cm, height: 5cm, record.at(0)))
  place(dx: 5cm, dy: 5cm, block(
    width: 2.3em,
    height: 0.7em,
    stroke: 1pt,
    record.at(1),
  ))
  place(dx: 2cm, dy: 9cm, box(
    width: 2.3em,
    height: 0.7em,
    fill: green,
    record.at(2),
  ))
  place(bottom + right, dx: -2cm, dy: -2cm, block(
    width: 2cm,
    height: 2cm,
    stroke: 2pt,
    record.at(3),
  ))
  pagebreak(weak: true)
}

This is a somewhat strange use case without proper context.

Where does “predefined size” come from? Can you just not specify the size? It will not overflow in that case.

The only diagnostic you can make is panic().

First of all, thank you very much for your reply.

There is nothing strange about it. Let me elaborate a bit more:
Imagine you want to print a lot of badges and you have a set of A4 template sheet papers to print on. You have to print onto a certain area of the A4 paper, where you pull off the self-adhesive part and then fold it. There is a design of said badge which defines things like:

“The name goes exactly here and the affiliation goes exactly there.”

Each of those fields have a per-design specified position and maximum size. This means if the content is to large we simply run out of room to print on. This happens e.g. when people provide a very long affiliation information which then necessitates manual abbreviation.
The data loading and iterative page build is something I omitted here, because I already know that this is easily possible with typst.

Well, I guess then panic() will do a great job preventing you from badly laid out badges.

You can also check out Using Typst for event badges and etykett – Typst Universe.

Thank you for your time. These are some interesting examples but my key problem is not addressed there.
With regards to the use of panic() I first have to figure out a way to detect the overfull condition in the first place. Maybe with measure() somehow.

You can try unsetting height and then do #context measure(block(width: 4cm, lorem(13))). Seems to work.

Have you looked at one-liner – Typst Universe ? Could be giving ideas on how to deal with the problem rather than give an error ?

1 Like

Thanks for the suggestion but I’m dialing with line-wrapping boxes which have a height limit.

Ok, I found some solutions which solve my original question in three different ways. I combined them all into one example so anyone can pick and choose what fits best for them:

  1. In-Document-Warning (Summary): use a state to collect and typeset a warning summary
  2. In-Document-Warning (Outline): use invisible headings to record the warnings into the outline
  3. Metadata: put the warning into a metadata label which can then be queried from the CLI
// file: overfull.typ
// --- page setup ---
#set page(width: 10cm, height: 6cm)

// --- example dataset ---
#let records = (
  (firstname: "Alice", lastname: "Anderson", affiliation: "ACME"),
  (firstname: "Bob", lastname: "Baker", affiliation: "A Company that Manufactures Everything\ - Sales Department -"),
)
 
// In-Document-Warning (Summary): create a state
#let overfull_box_on_pages = state("overfull", ())

// --- overfull aware box (vertical limit detection) ---
#let box_vlim(body, width: auto, height: auto, highlight: red, ..box_args) = layout(
parent_size => {
  let width = width
  if width == auto {
    width = parent_size.width
  }
  let boxed = box(body, width: width, ..box_args)
  let size = measure(boxed)
  if type(height) == length and size.height.mm() > height.to-absolute().mm() {
    boxed = box(body, width: width, height: height, fill: highlight, ..box_args)
    // Metadata: this can be queried from the CLI with:
    // `typst query overfull.typ "<overfull>" --field value`
    [#metadata(here().page())<overfull>]

    // In-Document-Warning (Summary): collect page information into state
    let pages = overfull_box_on_pages.get()
    pages.push(here().page())
    overfull_box_on_pages.update(pages)

    // In-Document-Warning (Outline): create an invisible heading which shows up in the outline
    // https://forum.typst.app/t/how-to-add-an-invisible-heading-to-an-outline/685/2?u=jacob
    {
      show heading: none
      heading(numbering: none)[Overfull Box]
    }
  }
  boxed
})

// interpret dataset fields as markup
#let typeset(src) = {
  eval(src, mode: "markup")
}

// construct document page by page
#for i in range(records.len()) {
  box_vlim(height: 2em)[
    #typeset(records.at(i).firstname)\
    #typeset(records.at(i).lastname)
  ]+"\n"
  box_vlim(height: 1em)[
    #typeset(records.at(i).affiliation)
  ]
  if i < (records.len() - 1) {
    pagebreak()
  }
}

// In-Document-Warning (Outline, Summary): show outline and summary
#context {
  let pages = overfull_box_on_pages.final()
  if pages != () {
    pagebreak()
    outline(title: "Warnings")
    pagebreak()
    [= Warnings Summary]
    [overfull box on pages: #pages]
  }
}
1 Like