#Balanced columns?

48 messages Β· Page 1 of 1 (latest)

inland fog
#

I saw a post on Reddit earlier today in which it was brought to my attention that Typst does not yet have out-of-the-box support for "balanced columns". Behold, I have implemented it! Just wrap a columns(...) in make_balanced() (or do #show columns: element=>make_balanced(element) to affect all columns(...) objects within the current scope) and the columns will be confined to a box(...) that sets its height such that all columns will have a (roughly) equal amount of content!

It works in 5 simple steps:

  1. Based on the desired number of columns and the available width (of the page or other parent container) as well as the gutter width (supports relative and absolute forms!), determine how wide each column will be. This of course assumes that the columns will be of equal width.
  2. For each content item, hypothetically lay out the item in a box whose width is equal to the individual column width. Record the resulting element heights.
  3. The sum of these heights (and potential paragraph breaks between items) is the total content height.
  4. Divide the content height by the number of columns to get the "target height" for a column (and thus for the entire columns(...) object).
  5. Set the outer box's height to this "target height" value.
fervent holly
#

Wow that's awesome! Very useful thing for it will be πŸ™‚

jade kindle
#

I did something similar before, but it doesn't work so well in practice

#

?r t=l ```
#let multi-cols(col-count, gutter: 4%, body) = layout(((width,height)) => {
let gutter = gutter + 0pt + 0%
let gutter = gutter.ratio * width + gutter.length
let col-width = (width - (col-count - 1) * gutter)/col-count
let total-height = measure(box(width: col-width, body)).height
box(height: total-height/col-count, columns(col-count, gutter: gutter, body))
})

#box(stroke: 1pt,
multi-cols(2, lorem(55))
)

#box(stroke: 1pt,
multi-cols(2, [
#lorem(50)

#lorem(60)

])
)

jade kindle
#

Did you manage to fix that problem? If so, how?

#

In my version, I added an adjustment argument, to manually adjust the box's height to fit everything

#

?r t=l ```
#let multi-cols(col-count, gutter: 4%, adjustment: 0pt, body) = layout(((width,height)) => {
let gutter = gutter + 0pt + 0%
let gutter = gutter.ratio * width + gutter.length
let col-width = (width - (col-count - 1) * gutter)/col-count
let total-height = measure(box(width: col-width, body)).height
box(height: (total-height + adjustment)/col-count, columns(col-count, gutter: gutter, body))
})

#box(stroke: 1pt,
multi-cols(2, adjustment: 1em, lorem(55))
)

#box(stroke: 1pt,
multi-cols(2, adjustment: 2em, [
#lorem(50)

#lorem(60)

])
)

robust yacht
#

Yeah I did something similar right after typst was launched publicly, and I've seen others do it as well. It's unfortunately very brittle

inland fog
#

I split the body into paragraphs for measuring. Not sure how much of a difference that makes, but I didn't need an "adjustment" parameter

#

I also have some half-worked logic involving #show rules for par, parbreak, and the non-breaking spaces/hyphens, but it appeared not to be necessary to go down that route any further.

#

There's also a bit of hackery I tried at first that used dynamically-numbered labels and query to check for overflow, but I eliminated that when I thought of this simpler way. No reason I can't add it back, though.

inland fog
robust yacht
inland fog
#

How many themes are there?

potent knoll
#

Type ?help render in #bot-corner

jade kindle
#

@inland fog would you mind sharing your code? I would be interested in seeing how you did it

subtle ember
#

from reddit:

I'll definitely publish it on Github at the very least once I clean it up a bit. There's some vestigial junk left over from the detour I took with trying to manipulate all spaces/hyphenations.

jade kindle
#

I see, thanks for the heads up

jade kindle
wide sonnet
jade kindle
#

Never! πŸ™ƒ ||I actually like justified text, but this is just a minimal example||

inland fog
#

I spent way too much time today trying to figure out how to use context to check whether or not there's content that extends past the end of the box.

#

I put together this updated demonstration document (added a feature that improves behavior when the top-edge and bottom-edge are not their defaults, and/or there is justification happening)

#

Oh and I also tested whether or not it works with wrap-it and the answer is... sometimes but there's not much reason to use it in my opinion (since wrapping multiple-column text around a figure looks best when the figure is able to span multiple columns, but I haven't implemented support for that in my balanced columns).

inland fog
#

HOLD UP. I THINK I DID IT

#

I THINK I FIGURED OUT HOW TO DETECT AN OVERFLOW

#

There are convergence issues when the content has more than one paragraph for some reason, but everything looks fine πŸ€”

inland fog
#

@fervent holly@jade kindle@robust yacht@subtle ember I'm going to add some configuration options to turn on/off extra constraint features and also add an adjustment parameter (I know, sad day!) to allow adding/removing lines from the container height. But other than that, I think I'm done wrestling with this for now. https://typst.app/project/r9CwGi2AicjLsCIcrrX4ts

#

Still reports nonconvergence but looks ok in most cases (although certain cases require enabling/disabling a constraint or two)

#

Note that it can be quite slow to render due to the length of the document and the large quantity of balcols.

lime cargo
# inland fog <@1091457992551055421><@299795821824704512><@399269065388195842><@69100384060610...

This looks really good for the most part! However, I'm getting an odd issue where inside the balcol body all styles (bold, italic, headings, etc.) seem to get erased (turn into regular text). Is this a known issue/limitation or might I be doing something wrong?

Edit: Oddly enough, using e.g. #text(weight: "bold")[lorem ipsum] works, while *lorem ipsum* and #strong[lorem ipsum] do not.

inland fog
#

But items that don't expose their raw body text in a way that the algorithm expects will be treated as "words" themselves

#

Which could cause issues if they span multiple lines or fall at the beginning/end of an important line for column layout.

#

I actually went and made additional changes to the version shared here, which fixed a lot of issues and improved the rendering speed, but still got stumped with trying to figure out how to determine if 2 things are on the same page (or something like that, it's been a while).

#

I've started to become of the opinion that trying this hacky way of deconstructing everything is not the best way to achieve this since Typst is open-source and can be contributed to directly in Rust.

lime cargo
inland fog
#

The position and layout info of each word is much better acquired during the construction stage, rather than after everything is already put together and a lot of that information has been discarded and must be inferred or recalculated

lime cargo
inland fog
#

Yeah. There's no way to do it within Typst itself, I've become convinced, with the lack of introspection that is offered.

#

Also, some algorithms might require 6 passes, but Typst is capped at an arbitrary 5.

inland fog
#

Is everyone mostly happy with the way the page handles multiple columns?

#

All someone needs to do is make a version of box or block or whichever one allows page breaks that uses the page columns thingy

#

Like Latex's \minipage