m

meander

@preview

Page layout engine with image wrap-around and text threading.

v0.4.4
MIT

Package Information

Last Updated
Categories
layout

1. Get the package

Download the package using the TPIX CLI:

tpix get @preview/meander:0.4.4

2. Import in your Typst file

Add this to your .typ file:

#import "@preview/meander:0.4.4": *

Version History

0.4.4
17eff8ee00b8...
0.4.3
6087e817abb5...
0.4.2
5b8a7de7505e...
0.4.1
f31b2ddabe52...
0.4.0
2f6f3dbf41ec...
0.3.1
15babf628f19...
0.3.0
c1701d7c0eea...
0.2.5
f53a4b1d43ac...
0.2.4
62aec7c63740...
0.2.3
d05d4237ee2c...
0.2.2
e18a1e564929...
0.2.1
c69cda5c18ce...
0.2.0
ef1d7c507c32...
0.1.0
26a7525ca2d2...

Meander

meander provides a core function reflow to segment pages and wrap content around images.

See the documentation.

Quick start

The function meander.reflow takes a sequence of

  • obstacles: use placed to put content at specific positions of the page,
  • containers: use container to specify where text can be laid out,
  • flowing content: provide text with content.
  • optionally pagebreak, colbreak, colfill can be used to produce multi-page
    layouts and fine-tune which text goes into which container.
#let my-img-1 = box(width: 7cm, height: 7cm, fill: orange)
#let my-img-2 = box(width: 5cm, height: 3cm, fill: blue)
#let my-img-3 = box(width: 8cm, height: 4cm, fill: green)
#let my-img-4 = box(width: 5cm, height: 5cm, fill: red)
#let my-img-5 = box(width: 4cm, height: 3cm, fill: yellow)

#import "@preview/meander:0.4.4"

#meander.reflow({
  import meander: *

  // As many obstacles as you want
  placed(top + left, my-img-1)
  placed(top + right, my-img-2)
  placed(horizon + right, my-img-3)
  placed(bottom + left, my-img-4)
  placed(bottom + left, dx: 32%, my-img-5)

  // The container wraps around all
  container()
  content[
    #set par(justify: true)
    #lorem(430)
  ]
})

a page where text flows between 5 rectangular obstacles


Use multiple containers to produce layouts in columns.

#let my-img-1 = box(width: 7cm, height: 7cm, fill: orange)
#let my-img-2 = box(width: 5cm, height: 3cm, fill: blue)
#let my-img-3 = box(width: 8cm, height: 4cm, fill: green)

#import "@preview/meander:0.4.4"

#meander.reflow({
  import meander: *

  placed(bottom + right, my-img-1)
  placed(center + horizon, my-img-2)
  placed(top + right, my-img-3)

  // With two containers we can
  // emulate two columns.

  // The first container takes 60%
  // of the page width.
  container(width: 60%, margin: 5mm)
  // The second container automatically
  // fills the remaining space.
  container()

  content[#lorem(470)]
})

a two-column page with 3 obstacles


If your Meander environment shares page(s) with other content,
use the option placement: box.
The overflow parameter determines what happens to text that doesn't
fit inside the provided containers.

You can see this in effect in the example below:

  • the text in red is outside of the Meander environment
  • the text in blue is the Meander environment itself
  • the text in black is the overflow handled by Meander
#import "@preview/meander:0.4.4"
#set par(justify: true)

#text(fill: red)[#lorem(200)]

#meander.reflow({
  import meander: *
  // Gets rid of the paragraph break between
  // the columns and the overflow.
  opt.placement.spacing(below: 0.65em)

  // This turns on some debugging information,
  // specifically showing the boundaries
  // of the boxes in green.
  opt.debug.post-thread()

  container(
    width: 48%,height: 50%,
    style: (text-fill: blue),
  )
  container(
    width: 48%, height: 50%, align: right,
    style: (text-fill: blue),
  )

  content[#lorem(700)]

  // This applies a style to the text
  // that overflows the layout.
  opt.overflow.custom(data => {
    set text(fill: orange)
    data.styled
  })
})

#text(fill: red)[#lorem(200)]

Content that overflows the environment (page 1/2)
Content that overflows the environment (page 2/2)


Meander allows precise control over the boundaries of obstacles, to draw complex paragraph shapes.

#import "@preview/meander:0.4.4"

#meander.reflow({
  import meander: *

  placed(
    center + horizon,
    boundary:
      // Override the default margin
      contour.margin(1cm) +
      // Then redraw the shape as a grid
      contour.grid(
        // 25 vertical and horizontal subdivisions.
        // Just pick a number that looks good.
        // A good rule of thumb is to start with obstacles
        // about as high as one line of text.
        div: 25,
        // Equation for a circle of center (0.5, 0.5) and radius 0.5
        (x, y) => calc.pow(2 * x - 1, 2) + calc.pow(2 * y - 1, 2) <= 1
      ),
    // Underlying object
    circle(radius: 3cm, fill: yellow),
  )

  container(width: 48%)
  container(align: right, width: 48%)
  content[
    #set par(justify: true)
    #lorem(570)
  ]
})

text with a circular cutout


For a more in-depth introduction, including

  • alternative recontouring techniques,
  • styling options,
  • advanced multi-page layouts,
  • control over content that overflows,
  • tips to get better segmentation,

please consult the documentation.