Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New flow layout, with multi-column floats #5017

Merged
merged 3 commits into from
Sep 25, 2024
Merged

New flow layout, with multi-column floats #5017

merged 3 commits into from
Sep 25, 2024

Conversation

laurmaedje
Copy link
Member

@laurmaedje laurmaedje commented Sep 24, 2024

This is a full, fundamental redesign and rewrite of Typst's most central layouter, the flow layouter. This layouter is responsible for arranging paragraphs, blocks, spacing, placed elements, floats, and footnotes across pages.

Why a new flow layouter?

The old flow layouter was written a long time ago and grew over time. New features, like footnotes and floats, were mostly bolted on top: This showed up in a lack of features (e.g. floats over two columns) and bugs (e.g. footnotes in breakable blocks flowing away to the last page of the block). To address those properly required a fundamental shift in how the flow operates. The old flow layout proceeded in a strictly linear manner: It processed elements one by one, with no way to go back and revise a decision. This mode of operation is quite limiting and can fundamentally not handle all features we want Typst to have.

How does it work?

The new flow layout operates in a sort of flipped-around way: Instead of walking elements one by one, it walks pages one by one. For each page, it fills it with content as long as space is available. And here comes the crucial part: While laying out a page, it can decide to trigger a relayout of the page. This happens when a float or footnote is inserted, as this can affect the layout of the page's inner content. I wrote a bit more about this approach in a blog post some time ago: In summary, the approach of gaining information and then relayouting with that new information can help us iteratively solve various problems that can't be solved in a single step. It's sort of similar to how Typst already runs multiple layout iterations to resolve introspection, but additional and add the page-level. (It might seem like a good idea to actually use the normal introspection for these things, but one runs into convergence issues immediately, which is why the iteration scope must be tighter.)

What does it do?

User-facing, this PR

  • Adds new page-scoped floating placement
  • Adds a new public block.sticky to property to prevent a pagebreak after a block. This is used internally by headings.
  • The height of blocks, images, and some shapes can now be specified in fr units.
  • Fixes many bugs, the most important ones being:
    • No more orphaned headings at the bottom of the page.
    • Footnotes in breakable blocks now work properly. It can still happen that a footnote is pushed to the next page due to a lack of space, but it's much rarer.

The new floating placement looks like this:

#set page(columns: 2)

// Raw placement.
#place(
  top,
  scope: "page",
  float: true,
  [This spans both columns]
)

// With a figure.
#figure(
  placement: auto,
  scope: "page",
  caption: [My caption],
  rect(width: 75%),
)

#lorem(200)

The PR also lays the groundwork for exciting future features like to-the-side wrapping floats, layout through chained containers, page margins depending on the page number, and more.

Notes

  • In my very unscientific testing on a few documents, performance was not affected much. While working on the PR, I primarily on focused on getting things right and not hurting performance too much, so that's a good result I would say. Fundamentally, doing relayouts is of course more expensive than not doing them.

  • The PR also adds the new file tests/skip.txt, which can be used to temporarily disable all the tests listed within. This is very useful during large incremental refactors.

Linked issues

@jamesrswift
Copy link

Hecking AWESOME

@rtbs-dev
Copy link

rtbs-dev commented Sep 24, 2024

Y'all, it's HAPPENING
It's happening!

@laurmaedje laurmaedje added this pull request to the merge queue Sep 25, 2024
Merged via the queue into main with commit e25389a Sep 25, 2024
12 checks passed
@spped2000
Copy link

Cool!

@laurmaedje laurmaedje deleted the flow-rewrite branch September 25, 2024 09:28
@0x7CFE
Copy link

0x7CFE commented Sep 25, 2024

Finally! Thanks for the hard work.

@RomeoV
Copy link

RomeoV commented Oct 28, 2024

FYI, in the example given by @laurmaedje above I believe scope: "page" has since become scope: "parent".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment