How does `scale` work within grids?

We get weird result because

  • there’s no enough space to put sq — the height of sq is 15 mm, but only 10 mm is available in the row (expect the first row)
  • the scale factor is specified as an absolute length, instead of ratio.

According to the rust code, I think the relation between these heights is the following:

#let calc-result-height(
  available-height,
  content-height,
  scale-abs,
) = {
  let scale-ratio = (
    scale-abs / calc.min(available-height, content-height)
  )
  content-height * scale-ratio
}

Code
#let calc-result-height(available: 0pt, content: 0pt, scale-abs: 0pt) = {
  // Here `0pt` are only placeholders. This function only expects positive lengths.
  let scale-ratio = scale-abs / calc.min(available, content)
  content * scale-ratio
}

#let scale-abs = 10pt
#let content-height = 10pt
#let available-heights = (20pt, 15pt, 10pt, 8pt, 4pt, 2pt, 1pt, 0.5pt)

#assert.eq(type(scale-abs), length)

#let it = scale(scale-abs, box(
  fill: orange.transparentize(50%),
  width: 10pt,
  height: content-height,
))

#set page(width: auto, height: auto, margin: 1em)

A #content-height square: #box(width: 10pt, height: content-height, fill: purple, baseline: 20%)

#let raw-repr(it) = raw(repr(it), lang: "typc")

#table(
  columns: 2 + available-heights.len(),
  align: (x, _) => if x > 0 { center } else { start } + horizon,
  stroke: (x, y) => if y > 0 { (top: 0.5pt) } + if x > 0 { (left: 0.5pt) },

  [*Height of \ available space*],
  $+oo$,
  ..available-heights.map(raw-repr),

  [
    *Height of* \
    #raw(
      lang: "typc",
      ```typc
      scale(
        {scale-abs},
        box(height: {content-height})
      )```
        .text
        .replace("{scale-abs}", repr(scale-abs))
        .replace("{content-height}", repr(content-height)),
    )
  ],
  grid(
    align: horizon,
    columns: 4,
    column-gutter: 2pt,
    raw-repr(content-height),
    box(width: 0.5pt, height: content-height, fill: black),
    it,
  ),
  ..available-heights.map(h => {
    let result = calc-result-height(
      available: h,
      content: content-height,
      scale-abs: scale-abs,
    )

    grid(
      align: horizon,
      columns: 4,
      column-gutter: 2pt,
      raw-repr(result),
      box(width: 0.5pt, height: result, fill: black),
      box(height: h, stroke: gray, it),
    )
  }),
)

My conclusions

(for typst 0.13.1)

Short answers to specific questions

Why is the “square” in row 2 rectangular and not square?
row 2: scale(sz, sq)

  • width: 5 mm, as specified.

  • height: there’s no enough space to put the original 15 mm sq, and the result height is determined by:

    let scale-ratio = scale-abs / calc.min(available-height, content-height)
    // = 5mm / calc.min(10mm, 15mm)
    // = 50%
    
    let result-height = content-height * scale-ratio
    // = 15mm * 50%
    // = 7.5mm
    

Why is the square in row 3 not 5mm x 5mm?
row 3: scale(sz, x: auto, sq)

auto means to preserve the aspect ratio.

  1. height: 7.5 mm, the same as row 2.
  2. width: 15 mm * 50% = 7.5 mm, as implied by x: auto, which means the same ratio in y axis.

Why is the factor in row 7 ignored and width and height are different?
row 7: scale(sz, x: 100%, sq)

The precedence of factor, x, y is:

  • Resolved x = first value that is not none in the list (x, factor, 100%).
  • Resolved y = first value that is not none in the list (y, factor, 100%).

Therefore,

  • width: 15 mm * 100% = 15 mm, as specified by x: 100% — it’s relative to the original size of the content.
  • height: 7.5 mm, the same as row 2.

What is going on in row 8?
row 8: scale(sz, x:1.5 * sz, y: 1.1 * sz, sq)

As explained in row 7, the resolved x is 1.5 * sz, and resolved y is 1.1 * sz.

  • width: 1.5 * 5 mm = 7.5 mm, as specified by x: 1.5 * sz.
  • height: 15mm * ((1.1 * 5mm) / 10mm), as specified by y: 1.1 * sz and similar to row 2.

Why is the square height in row 10 and 12 larger than the row height even though the y parameter is 100%?
row 10: scale(sz, y: 100%, sq)
row 12: scale(sz, x: 100%, y: 100%, sq)

ratios are relative to the original size of the content, not available region.

Row 10:

  • width: 5 mm, as specified.
  • height: 15 mm * 100% = 15 mm, as specified.

Row 12:

  • width: 15 mm * 100% = 15 mm, as specified.
  • height: 15 mm * 100% = 15 mm, as specified.

Answer to general questions

  1. Resolve (factor, x, y) into (x, y).

    • Resolved x = first value that is not none in the list (x, factor, 100%).
    • Resolved y = first value that is not none in the list (y, factor, 100%).

    Here factor, x, y can be auto | length | ratio.

  2. Convert auto | length | ratio to ratio.

    1. lengthratio: As explained in row 2, length ↦ length / min(available-size, content-size).
    2. autoratio: Use the ratio in the other axis, keeping the aspect ratio.
    3. ratio is relative to content-size.
1 Like