Erna - a Typst library to scale and align rows of images

I’ve recently discovered Typst, immediately fell in love, and as a way of learning it, I’ve put together a little library. I called it Erna. You can check it out here:

Given some images it:

  • fills all the available width;
  • arrange the images in a rectangle;
  • preserve all aspect ratios.

Like this:

So I want to show and tell, but I also have a few questions.

Am I re-inventing the wheel?

It would be ok, since it was a learning project and a brain-teaser for me, but after cursory research I couldn’t find anything that does what I want. The closest I could find is tessera – Typst Universe, but I don’t think it can keep the cells in a row equally high. Also, because it’s more general-purpose, its API seems more complicated. With Erna I aim for simplicity and non-tech-user friendliness, even at the expense of flexibility.

Code review

I’d appreciate any pointers regarding my implementation. Esp. around the solve function. What I’m currently doing is representing the problem of finding the height and width of every cell as a set of linear equations. I think there is a straight-forward way to solve them, but I haven’t figured it out yet. So instead Erna is recursively dividing the range of possible heights until it reaches approximately right result. It seems performant enough. See the playground.pdf document in the repository to follow my somewhat convoluted thought process.

If there is a better approach, how could I benchmark it against the current one?

Is Erna a promising material for a package?

If it’s not re-inventing the wheel, and the code is not entirely rubbish, should I try to publish it as an @preview package? Any tips on that?

16 Likes

Nice package and very useful. I think it would be great to add it to Typst Universe. I would like to use it just importing like other packages.

For code review I think it is better you make a post (or make questions on Discord) exclusively for that and hopefully someone could help you.

4 Likes

cool! reminds me of a script I used some time ago:

they published some math, that gave really good results.

4 Likes

Thanks! It looks like what I’m looking for, but I haven’t had time to try it yet.

In the meantime I made a little logo for the package. What do you all think?

1 Like

reads like Enra to me, but that me on purpose.

I read left right, top down; not top down, left right

2 Likes

I had exactly the same thought… "But s/he called it ‘Erna’ not ‘Enra’ ". I would guess that left-right, top-down is how the majority of people read.

Fair enough @Astrusia and @Mark_Hilton.

The reason behind top-down design was that it would correspond with the code:

#image_row((E, r), (n, a))

Where E and r would make a stack on the left, while n and a would stack on the right. But that’s certainly too nerdy :sweat_smile: Thank you for your feedback.

2 Likes

I mean, you can just change how args are handled, though the name is confusing, and I wouldn’t think that 2 args are 2 columns. Or do

#image-row(..(E, r).zip((n, a)))
#image-row(..array.zip(([E], [r]), ([n], [a])))

Naming things is difficult, esp in a way that is intuitive to lay people or beginners, for whom Erna is addressed. Being exact is not always the best method.

In the docs I don’t call those things “columns” but “stacks”. So technically the image_row function produces a row of stacks, but that wouldn’t make a good name. The simplest use case is a row of singletons i.e. one image per stack. I hope that by calling it image_row I make this use case easy to understand for beginners, while advanced users like you can hopefully figure out the stacks feature.

Actually, I am one of the “beginners” you refer to, and I have to say that

had me totally confused. I had to read what follows very carefully and think about it for quite some time. As a comparative beginner I was looking at it in the way I read. It was only when I realised what was in essence a cell in a row contained a “stack” of two elements that I finally understood what image_row referred to.

As for the name, naming is difficult, and while I have no problem with it as a name, someone like me looking for a package to lay out images who came across a package called Erna would not immediately relate it to what they wanted.

:slight_smile:
Mark

2 Likes

Even if the notion of stack is not immediately obvious, it actually becomes much clearer after carefully reading the documentation here:
https://codeberg.org/tad-lispy/erna/src/branch/main/preview.pdf

However, if I may, what I still did not understand is the syntax for defining multiple rows. Even after going through the documentation and its final example, this part remains unclear to me.

Thank you for this project. I think it will be very useful.

2 Likes
  • Tessera is a layout pkg for images, but there are many
image("i.png")  // functions, the api is a bit complex.
  • While Erna looks like a new layout style of Tessera.

Perhaps u could collaborate with F.X.P. on Tessera pkg, start by a github issue.

if 1 + 1 > 2 {collaborate()}  // if u trust the formula
else {yet-another()}  // be sure to follow pkg register doc
1 Like

Hey, David! Thank you for your kind words, and sorry for the late response.

Yes, the description of multiple rows feature is a bit sparse. I think I was tired and short on time when writing it. Let me try to improve. So the image_rows (plural) is essentially like calling image_row (singular) multiple times. You could write it like this:

#image_row(a, b, c)
#image_row(d, e)

or like this:

#image_rows(
  (a, b, c), 
  (d, e)
)

To get from a series of image_row to one image_rows, just copy the arguments list (together with the parens) of every row and paste it together as arguments of image_rows.

The use case for this is setting the same gutter size everywhere, without having to repeat yourself. In the future there might also be other shared features, like rounded corners, or borders. With image_rows (plural) you can configure them once and apply them to every row.

Please let me know if it makes sense, so I can include it in the docs.

Thank you for your answer!

I think my confusion comes from the fact that the same syntax seems to be used for two different purposes in the documentation.

On the one hand, parentheses are used to define a stack within a row. On the other hand, they also seem to be used to define separate rows in image_rows.

For example, why does

#image_rows(
(a, b, c),
(d, e)
)

produce multiple rows, while (d, e) is interpreted as a single row containing a stack of two elements?

More generally, how does Typst distinguish between “this is a row” and “this is a stack” when the syntax looks identical?

If you look at the code of the two functions, you will see one is calling the other.

image-rows() is a wrapper function creating a stack (really it is a grid of 1 column) of image-row.

See The code for image-rows().

1 Like

Sorry about that… I just realized I missed the “s” in the name image_rows. :sweat_smile: