#Tables

1 messages · Page 2 of 1

grand haven
#

Is tablex still going to be a thing?

wary knot
#

yes

#

there are things we wont be able to provide natively

wary knot
#

right, 2 isn't fully accurate

#

and actually seems like I may have missed that on tablex lol

#

but also inset is 0 on grid i think

#

so theres that

grand haven
#

Yes, its a table / matrix typesetting library built on top of pgf/tikz

wary knot
#

lol

#

someone mentioned that package recently in tablex issues

#

thought it was only for matrices

grand haven
#

Ludicrously slow, so I've never really used it. But it does produce nice output

grand haven
#

I can't see any reason to use it over tabularray for actual tables though....

wary knot
#

guess I'll just #[allow] it for now

#

can probably refactor during reviews if needed

frozen oyster
#

Is this rust tablex? 😄

wary knot
#

baby steps 🙂

frozen oyster
wary knot
#

right now im here

frozen oyster
wary knot
#

this should be particularly quick actually

#

maybe first PR in a few days? lol

frozen oyster
wary knot
#

the difference between table and grid code is really small

#

i'm just copying stuff around mostly

#

well, and refactoring

wary knot
upbeat pine
upbeat pine
wary knot
#

i tried to be very conservative with this PR so I barely changed the stuff I moved

#

table.cell PR will be a bit less conservative

upbeat pine
upbeat pine
wary knot
#

though reviews and stuff exist still so yea

#

:p

wary knot
#

one question that arrived: for per-cell customization, should we apply align and inset before or after the cell show rule?

#

rn im working with before

#

so you can fully customize, even override the align and inset with show rules

#

however in principle fill would be post-show rule

#

hmmm

#

although that could be annoying so im not sure

#

well

#

you will have access to the cell's align and inset values

#

but maybe that's too much of a burden, to have to manually #align(cell.align, box(inset: inset)[content])

#

on the upside that would give more flexibility

#

and even allow for a 3rd-party diagbox to work for example

upbeat pine
upbeat pine
wary knot
#

v0.10.0 tables dont have cell show rules

#

so we are deciding that for the first time :p

upbeat pine
wary knot
#

huh?

#

you mean #show table: it => align(center, it)?

upbeat pine
#

When you do #set table(align: center) does it align before or after table show rule?

wary knot
#

neither because theres no show rule here

#

youre just setting the align parameter for tables

#

for tables which dont pick it at least

upbeat pine
#

It's suddenly become too hard, I lost ability to think.

wary knot
#

and the align and inset parameters, internally, cause the table to wrap cells in align(chosen-alignment, pad(..inset, cell))

#

which is of course expected

#

otherwise the parameters would be useless

#

:p

upbeat pine
#

I'll go back to this discussion later (today, for me).

wary knot
#

yeah sure

wary knot
#

well ive done enough for today 😂

#

ive begun work on per-cell customization but it's very early still

wary knot
#

ok

#

this discussion is now item 2.1.5 in the document

wary knot
#

Pc is off now otherwise I’d update it again

upbeat pine
#

Did we discuss rotated cells? Because I'm currently dying trying to make a table look fine, let alone perfect.

wary knot
upbeat pine
#

Well, yes? Just a small text cell that need to be rotated -90°.

wary knot
#

That’ll be possible in the next update with rotate(reflow: true)

#

since it affects layout

#

For now you can use a workaround to manually resize the contents

#

See tablex’s “known issues” in the readme for info (applies to native tables too)

upbeat pine
#

I'm using this abomination:

#let rotate-cell(body, before: 3.7em, after: 3.7em, width: 9em) = {
  box(v(before) + rotate(-90deg, box(width: width, body)) + v(after))
}
wary knot
#

More accurate and general workaround

upbeat pine
#

This went hell to heaven real fckn quick.

wary knot
#

np 👍

random mica
robust viper
#

could you elaborate on this problem in this context? I'm just trying to find out if the non-text show rule recursion is actually a problem or if most of its potential uses are actually mis-uses.

wary knot
#

But I mean, we could theoretically solve that with the data => … thing for each property, I just hadn’t thought of that yet

#

But also seems like a possibly relatively common pattern

#

However in this case I’m not sure if it’s a misuse or something, cuz the set rule being inside the show rule stops us from evaluating the data before the show rule, and internally this can have consequences

#

Either way recursion wouldn’t be useful here

wary knot
#

Thanks for pointing this out

#

I think that is the largest headache

#

Cuz doing that will cause infinite recursion

#

However the problem with the properties only being available in the show rule remains

#

So I’m not sure how good that is

robust viper
wary knot
#

I think it could be technically possible to do that without changing the cell’s body though, but you got me thinking now 😂

robust viper
#

I just mean that the alignment and inset needs to be wrapped around the whole cell rather than its body

#

Which necessarily means that the show rule styles are more inwards and the align/inset styles more outwards

#

(First and second aren't really well-defined here, hence why I'm saying inwards and outwards.)

wary knot
#

Yeah fair enough

#

While it’s a bit sad to lose on the flexibility in that regard , it’s probably for the best

robust viper
#

Why is flexibility lost?

wary knot
#

Otherwise you’d always get some AlignElem with a PadElem or something

wary knot
#

Objectively speaking it is flexible, but not necessarily desirable

#

Noting here that show rules could be something external to each individual table

#

Maybe it’s some particular kind of cell which should have that changed

robust viper
#

couldn't you still do that? inset: 0pt is more or less a no-op. and for alignment the inner one wins.

wary knot
#

Hmmm

#

yeah you’d have to set inset to 0pt and fill to none, but otherwise it probably works

#

Not sure about the alignment but I think it should work too? I’ll test it

#

With that said I think your proposal is probably for the best, so I’ll probably implement it first

#

If someone else has another idea we can discuss later but I think the it.body argument was pretty hard to refute 😂

wary knot
#

Also huzzah everybody

#

First PR merged, that was so fast 😂

#

The benefits of doing all this planning are showing

wary knot
#

btw @robust viper have you seen the tablex PR in typst/packages?

robust viper
wary knot
#

Thanks ❤️

upbeat pine
wary knot
upbeat pine
# wary knot it is, someone PR'ed it to the "Known issues" section of the README

Yes, I see:

Alternatively, you may attempt to use the solution proposed at https://github.com/typst/typst/issues/528#issuecomment-1494318510 to define a rotatex function which produces a rotated element with the appropriate sizes, such that tablex may recognize its size accordingly and avoid visual glitches.
But I was thinking of including the workaround itself, because it's a documentation about tablex, I think it should directly document everything. You can link to the OP, but still include the snippet directly in the doc. This will be more safe, because you always have the doc file with you (at least you should, like books, but digital). On the Internet, it can become inaccessible because:

  • the OP changed the snippet;
  • the OP removed the snippet;
  • the GitHub is down at the moment;
  • you can't access the Internet for some reason.
wary knot
#

fair enough, but it wont need a workaround in the next Typst version anyway

#

so not sure if it's worth the trouble now

upbeat pine
#

If you don't release a new tablex version until it will be possible directly in the released Typst 0.11.0, then I don't see the point either.

upbeat pine
# wary knot one question that arrived: for per-cell customization, should we apply align and...

@silent If this is still relevant. Doesn't the show rule right now already have everything applied? As @robust viper have explained: it is the element with default styling, but it.body is unstyled raw content of the element. I think I'm probably missing something here. Maybe give an example of "apply before cell show rule" and "apply after cell show rule". Choosing based on visual context is much easier than on just a plain text.

#

(oopsie, misspelled the word silent)

wary knot
#

before the cells are even passed to the GridLayouter, which does all the table layout magic, their contents are directly converted to align(alignment(x, y), pad(..inset, body))

#

here cells arent even an element

#

theyre just the body

#

this means that the body is being directly transformed

#

if we keep that behavior when creating cell elements, this means that it.body will have align(..., pad(...))

#

cuz the body will be transformed before the show rule

#

but as Laurenz said this is likely not desirable

upbeat pine
wary knot
#

@robust viper do you think cells should automatically resolve per-cell alignment, inset and fill on show rules?
In other words, if you write

#show table.cell: it => panic(it.align, it.fill, it.inset)
#table([a])

Should it panic with auto, auto, auto or auto, none, 5pt?

#

(Right now I'm tending towards the latter, at least I think it'd be more useful)

robust viper
#

the latter seems more useful

#

does that also sidestep the issue with ordering?

#

i.e., does the default show rule apply alignment and inset or does the table do it?

wary knot
#

i believe it's a bit of an orthogonal problem

#

this will just affect the cell's fields pretty much

robust viper
#

if it's the former, the default show rule couldn't do it

#

since it wouldn't the know the values

#

it being the latter would make it possible

wary knot
#

oh right
I was thinking of not having the default show rule apply those things

#

I'm thinking a lot here on how to implement this in the best way possible lol so I'm trying to think of as many edge cases as possible

robust viper
#

that's good!

wary knot
#

rn I'm working with the idea of a GridCell trait (might have to rename due to grid's cell, but idk), for anything that can be used as a cell in the grid
Content implements it and is the most trivial case (it would just mock all the methods), but grid's and table's Cell elements would also implement it with non-mocked methods

#

regarding the field resolution thing, i was thinking of having some method like
resolve(x, y, default_fill, default_align, default_inset) , which would provide the cell with all relevant state so it can update its fields

robust viper
#

would the children fields remain of type Vec<Content> or would they become GridCell and TableCell with an automatic cast from content?

wary knot
#

they would become Vec<T>, T: GridCellTraitThing

#

T defaults to Content

#

but GridCell, TableCell have an auto cast from Content yes

robust viper
#

I mean on the GridElem and TableElem

wary knot
#

yeah

robust viper
#

(which can't be generic)

wary knot
#

it'd just specify auto for all fields then

#

aka inherit from table

robust viper
#

my question is what type is here?

wary knot
#

I believe it'd work similarly to ListItem here

robust viper
#

okay

wary knot
#

we'd use some GridCellElem kind of thing which has auto cast from Content

robust viper
#

I'd drop the Elem

wary knot
#

sure

robust viper
#

It's not typically used in the codebase for scoped elements right now

wary knot
#

i think GridCell might not be the ideal name for the trait then cuz the conflict is obvious

robust viper
#

Why would the trait need to be implemented for Content then?

#

If it's only ever a GridCell or a TableCell

wary knot
#

perhaps Cell?

wary knot
robust viper
#

Yeah Cell is a good name for the trait

robust viper
wary knot
#

so for Content it just mocks everything that isnt needed

robust viper
#

fair

wary knot
#

i mean

#

at first I had a newtype like SimpleGridCell

#

but then I realized I could just implement it directly on Content

#

lol

#

much simpler

robust viper
#

Are you making the layouter generic or &dyn?

wary knot
#

for now generic

robust viper
#

I'm a bit afraid of code and compile time bloat with generics

#

The dynamic dispatch overhead would probably be completely irrelevant

wary knot
#

hmmm

#

well we'd have 3 possible Ts, not sure if it's that bad?

#

well, there's also the fact that resolve() would require &mut self

#

so, initially , i thought of having GridLayouter take ownership of the cells (although the cells can contain references to Content if so desired)

robust viper
#

it could also be an enum

#

but anyway, it doesn't matter too much at this point

wary knot
#

i think it shouldnt be too hard to refactor later maybe

#

for comparison though, in tablex this field resolution is only done when the cell is effectively laid out

#

so it'd be auto, auto, auto, and cells wouldnt be mutable

#

but i think we can probably do better

#

:p

wary knot
#

alright

#

I think I had an idea

#

We could have some CellGrid type - or just some function to generate (cols, rows, cells) - which would be created before the GridLayouter itself

#

this type or function would basically be responsible for assigning positions to each cells, so it would be an ideal candidate for the position where we'd call resolve

#

tablex has a similar thing, it's its own file even in the 0.1.0 branch

#

(it will be required to allow arbitrary cell positioning)

#

I'll experiment with the idea

robust viper
#

so that the grid layouter has all the state it needs?

wary knot
#

yes

#

i mean theoretically this is more or less what we're doing on new, but this would be more borrow check-friendly :p
(since right now we dont have to mutate anything)

robust viper
#

sounds good, I also felt like something like that would be needed while I briefly started to implement this a while back

wary knot
#

yeah it's just a bit nicer overall in the code organization as well

#

let's see how this goes 🙂

random mica
#

Another interesting problem I thought of is that if you write show table.cell: some-other-element(..) the table must then wrap your content in another table.cell, no? At that point the show rule might immediately apply again

wary knot
#

The show rule would only be invoked when the cell is about to be laid out

random mica
#

I'm not sure what you mean by that

wary knot
#

Fixed

#

:p

#

Basically the cell will just lay out its body by default

#

But the show rule would override that

wary knot
#

gotta be the scariest thing to get an error in bibliography.rs after modifying Grid 😂

robust viper
#

as long as it's just a compile time error

#

wait until you get "failed to format citation (this is a bug)" at runtime

wary knot
#

im not gonna be waiting for it 😂

wary knot
#

okay so

#

it turns out designing the internal code structure while aiming for different goals (support grid/table and list/enum, but also support GridCell and TableCell, and other stuff) is a bit more difficult than I thought

#

lol

#

the resolve_cells thing is the only place which adds mutability and screws everything over

#

cuz the original &Vec<GridCell> or &Vec<Content>etc. isnt mutable

#

the easiest way around this is to just clone everything

#

though another way I considered could be to use the style chain: table's fill: ..., align: ... etc. would just be a set table.cell before applying the show rule

#

this seems smart, though there'd definitely be some questions regarding how to make that work nicely

#

in particular, we'd have to define (well, I guess we already have to define)

  • which should be prioritized, a user's set table.cell outside of the table or a table's fill: ... override? (The latter seems to make more sense in principle?)
  • this wouldnt work if the user manually specified table.cell(fill: auto) for example, since it'd always override the set rule, so we'd have to remove auto as a possible value. Not sure if that's good or bad. (probably not that bad since the user should just not specify the parameter then.)
#

eventually i faced so many borrow checking and mutability problems that I thought "okay, I need to think this differently" lol

wary knot
#

so maybe the resolve thing isn't needed after all

robust viper
#

I'm currently trying to get rid of it

wary knot
#

understood

grand haven
wary knot
#

i was trying to figure out how to use it in a non-cursed way

#

but i didnt have much success

#

:^)

#

after all, Synthesize still requires mut somewhere

#

im still a bit unsure regarding cloning all cells ever though

#

i considered using something like Cow or Arc

#

but idk

#

wouldnt be nice to expose that to the Typst side

robust viper
#

Cloning content is relatively cheap

wary knot
#

fair enough

#

though I guess we'd also clone fill, align, inset for every cell

#

but I dont think theres any way to go around that other than the auto, auto, auto way

#

¯_(ツ)_/¯

#

lovely

robust viper
wary knot
robust viper
#

you would need to construct one Styles object per unique fill-align-inset combination and chain that to the grid's style chain

wary knot
#

ah

#

hmm

#

and chain that to the grid's style chain
so, this is the problem

#

where exactly would I add this style?

#

change the styles object somewhere on Layout?

robust viper
#

whenever a grid cell is layouted, it gets passed the grid's styles

wary knot
#

right

#

hmm

#

perhaps each cell could assess Grid::fill_in(styles)?

#

the grid would have to chain its own parameters to the style chain for that to work though

#

or maybe i can just pass those parameters down regardless of styles

#

but then it'd become a more "ad hoc" thing

robust viper
#

so, if I understand correctly, the problem is that show grid.cell: it => it.align should output the alignment resolved via #set grid(align: ..), right?

wary knot
#

or on the grid itself (which I believe to be the more common case), yea

robust viper
#

what do you mean with "on the grid itself"?

wary knot
#

as in

#

without using a set rule

robust viper
#

ah yes

#

same thing

wary knot
#

ok

robust viper
#

so, first of all, to have it.align work right now, it needs to be inherent. so even if we pass it via style chain, we would need a naive Synthesize impl to make them available. that's what I'm currently changing, so this kind of Synthesize impl would be ok since there are others just like it.

wary knot
#

right now im doing some ad-hoc thing here which im not even sure works

#

ill test that now

#

at least for the first time I got something that compiles 😂

robust viper
#

what's also important is that inherently specified fields on grid.cell have precedence over the grid's config. a style chain based solution would get that for free.

#

however, a style chain solution also means that the properties are inherited if there is a nested grid

#

I think just manually setting the fields if they're unset is simplest for now

wary knot
#

okay, well, we have something going

#

kek

#

baby steps

#

ok so

#

show rules dont seem to be doing anything at all

#

probably I need to use Show for that to work, I assume?

robust viper
#

looking at it, I'm wondering whether grid.cell(align: right)[C] is idiomatic as opposed to align(right)[C]

wary knot
#

hmm

#

I mean, I guess normally you wouldnt write it like that

robust viper
#

But I guess it makes sense to have it for consistency

wary knot
#

yeah i think

#

not 100% sure on that either though

#

but it does make the field available in a show rule for example

#

so theres that

#

regarding Show, I guess I should probably convert the old Layout implementation to some layout_cell in Cell kind of thing? So as to apply align and inset after show rules

#

or maybe I should just add align() and inset() to Cell and have GridLayouter do it

#

we can assume they were already resolved anyway

robust viper
wary knot
#

Well

#

That’s definitely possible

#

It would just make it so the user would have to manually align(it.align, box(inset: it.inset, …))

#

I think

#

Which may or may not be desirable

robust viper
#

only if they write a cell show rule that throws away it

#

which you just shouldn't do typically

wary knot
#

Hmmm

#

Good point

robust viper
#

just like if you write show raw: it => it.text

#

no highlighting

wary knot
#

Fair enough

#

I think the styling rework will help here too

#

So the user could idk it.x = y maybe

wary knot
#

But it’s not like that’s needed atm

wary knot
#

I’m not sure if pad(..it.inset) works , does it?

robust viper
robust viper
wary knot
#

hmm. Interesting

#

Was thinking about the rest: things

#

But if it supports that then that’s good

wary knot
#

I think I’ll just place it in the Show rule then

robust viper
#

but in Rust code it's definitely the right element

wary knot
#

@robust viper what's the best way to layout the post-show rule Cell from the GridLayouter?

#

right now im requiring an implementation of Layout (supertrait), but that doesnt seem to be helpful for GridCell/TableCell

robust viper
#

You shouldn't apply the show rule yourself, that's the job of content's Layout impl.

wary knot
#

right

#

I tried to run .clone().pack().layout(...) but it seems two grid tests broke

#

🤔

#

i'll take a look

#

hmmm interesting

#

it seems that inserting an align(...) as a grid element isnt working properly 🤔

wary knot
#

okay

#

it works if i remove the .padded

#

very odd lol

#

only happens when using Show instead of Layout as well

wary knot
#

ok, it's specifically vertical alignment apparently

#

though it works with grid.cell(align: horizon)

#

hmmm

random mica
#

is it unnecessarily confusing that for tables you must use a table.cell to override a cell while for grids you must use a grid.cell?

#

like
I get it
but I almost want to think of show table.cell as css table > cell

#

I don't think discussing this could lead to any improvements in this regard but I was wondering if anyone feels similarly

wary knot
#

there isnt really a way to do that in Typst , so there isnt much of an option

#

using nested show rules would affect nested tables / grids too

#

so it would work a bit weirdly

random mica
#

well > in CSS specifically means direct descendant

wary knot
#

yea

#

thats what I meant, it doesnt exist in typst

random mica
#

indeed

wary knot
#

but the non-> version does

random mica
#

perhaps it one day might

wary knot
random mica
#

do we have a forge for a selectors overhaul?

wary knot
#

though i can imagine it'd be a bit weird to define like what is a direct descendant

#

well I mean

#

there is #1175894504146993304

random mica
#

not really the same

wary knot
#

it's linked though

#

well anyway

#

there's an issue requesting something similar though i think

random mica
wary knot
#

yea and show rules make stuff kinda blurry

random mica
#

I reckon it deserves its own forge

#

had something to do with pattern-matching / destructuring, can't find it from my phone though

robust viper
wary knot
#

ok so

#

i did some experimenting , and I think the problem I'm having is a bug

#

or at least some weird stuff

#

heres what I did to debug:

#

made this

#

exposed it

#

did that

#

result

#

🤔

#

though I cant reproduce this outside of Show #bot-corner message

robust viper
robust viper
#

The align should be around the pad, not inside of it.

wary knot
#

hmm

#

but how did it work before?

robust viper
#

?r ```
#table(
align: top,
rows: 50pt,
align(bottom)[Hello]
)

robust viper
#

the outer alignment wins

#

but if align is not specified, it doesn't happen

#

?r ```
#table(
rows: 50pt,
align(bottom)[Hello]
)

wary knot
#

hmmm

#

i believe it's still behaving a bit differently in any case

#

that SimpleTest element did not have any sort of custom alignment

robust viper
#

and a pad is like a block, it doesn't auto-expand to the full height

wary knot
#

right. but the same code to generate the pad in the Show is the code being used today to generate the pad in table content

#

thats whats confusing me

robust viper
#

but what's weird is that here the inner alignment wins

#

?r ```
#block(height: 50pt, fill: red)[
#align(top)[
#align(bottom)[ok]
]
]

robust viper
#

that's somehow inconsistent or I'm missing something

wary knot
#

basically like

#

my goal is to keep that behavior of not affecting align if a global grid align isnt specified

#

and it works nicely when we preprocess the cells when they're just Content, into more Content

#

but not when we're applying the pad inside Show

robust viper
#

?r ```
#table(
align: left,
fill: red,
stroke: blue,
inset: 5pt,
columns: 2,
[AAAAA], [BBBBB],
[A], [B],
align(right)[C], [D],
align(right)[E], [F],
align(horizon)[G], [A\ A\ A],
)

robust viper
#

same problem on main

wary knot
#

bruhv

#

;-;

#

i dont wanna break all grids ever 😂

#

maybe the blast radius is smaller if i dont pad when it's all 0, 0, 0, 0?

#

well i mean the blast radius would be null

robust viper
#

That should definitely be done

wary knot
#

cuz inset doesnt exist atm

robust viper
#

as an optimization in any case

wary knot
#

yeah fair enough

#

though should we retroactively apply this to tables?

robust viper
#

you also don't have to use padelem

#

you can do your own insetting

#

but we should probably rather fix pad somehow

wary knot
#

mayhaps

#

i think it doesnt hurt to do it on tables like

#

the current behavior just ignores align so

#

users probably noticed that

#

lol

robust viper
#

if you remove the align: left it works in the normal table

#

?r ```
#table(
fill: red,
stroke: blue,
inset: 5pt,
columns: 2,
[AAAAA], [BBBBB],
[A], [B],
align(right)[C], [D],
align(right)[E], [F],
align(horizon)[G], [A\ A\ A],
)

wary knot
#

ah

wary knot
#

that's sneaky

robust viper
#

I think it just sort of wins

wary knot
#

then... still odd ;-;

#

idk

#

well

#

i'll just not pad if possible

#

lol

robust viper
#

but the padding doesn't seem to be the problem then

wary knot
#

im pretty sure its not doing any alignment as i added a debug print there

#

either way just not padding works for me

robust viper
#

and it works

wary knot
#

yeah. thats the weirdest thing

#

on main it's just mapping Content to pad(align(...))

#

in my current impl it's the default Show

#

so maybe theres some sort of "layout UB" at play here

#

lol

#

yeah ill just not pad when possible for now

#

i might open an issue later regarding this stuff, doesnt seem like it would be easy to solve though

#

also show rules work too

#

"mini synthesize" seems to be working too

#

yay

#

🚀

wary knot
#

tbh i considered moving fill to Show as well. but im not sure if that'd be desired

wary knot
# wary knot nuuuu

but also this is a bit of a dumb limitation, cuz we cant tell Rust that we're borrowing different fields here, so Rust just assumes we're borrowing all of them

#

bad crabby crab

#

i think moving self.cell to a top-level function (to which we provide the required fields) can probably work

vernal herald
#

time to employ references-by-indices trick.

wary knot
#

well

#

self.cell basically calculates the index

#

so it's pretty simple

#

actually the major reason it exists in the first place, as I see it, is that gutter is weird

#

that will change in due time

#

and also cuz assert!(x is valid and y is valid)

#

an easy way out of this is to just replace the relevant fields (cols, rows, cells, ...) by a single struct (in this case CellGrid), which would have the required info for a self.grid.cell(x, y)

#

I'll consider it

#

but for now I shall rest 🫡

#

most of the hard work for per-cell customization is already done though

#

just missing fill here and we'll have an initial PR (without x, y fields for now)

wary knot
#

nice

#

PR almost ready

wary knot
#

@robust viper is it fine for GridCell, TableCell to implement both Show and Layout (which just calls .clone().pack().layout(...))? (Only Show goes in the #[elem(...)] macro, but GridLayouter uses cell.layout(...) and cell.measure(...) a lot, so I thought it'd be redundant to add measure and layout to trait Cell instead of just using a supertrait, however that could still be desirable)

wary knot
#

this was fun lol

#

note that:

#

I didn't implement x, y fields yet, those require some separate changes so I thought a separate PR is ideal

vernal herald
#

You're also implementing it in typst? That's nice.

wary knot
#

it what?

#

the new table features?

#

if so: yeah 🙂
it's pretty much what we're all here for haha

#

tablex has had those features for a while now, but within native tables they'll be supercharged by Rust 🚀

wary knot
#

also I'm beginning to think that Line customization might have to come before Merging cells 🤔

#

cuz merging cells will require line splitting and stuff

#

unless i find a simple way to do it

#

maybe i will

#

i might experiment with this later

#

ehh.. yeah tbh the order doesnt matter actually

#

i'll just do Merging cells first

wary knot
#

So

#

basically I noticed that coordinates were off with my current PR when using gutters
Cuz self.cols changes after creating the gutter columns

So I applied a fix (the additions at the top) which just accounts for the gutters

But that magically fixes the issue mentioned above regarding cell coordinates when gutter is enabled, so I applied the additions at the bottom to "keep" it

#

I wonder if it's desirable to just not apply the bottom half of the patch instead, and fix the issue?

#

I mean, this will only affect .resolve_cell anyway (and thus per-cell alignment, inset, fill), GridLayouter doesnt care :p

#

Just to note: An "ideal fix" for me would be to keep gutter columns and rows separate from normal columns and rows, so we dont have to keep adding those coordinate adjustments everywhere, but maybe just fixing the coordinates reported to each cell (like I do above, in the top half of the patch) works in the meantime...

wary knot
#

Cuz fill was being calculated after the table was laid out and stuff (and thus using columns with gutters), but now I'm doing it before

#

So yeah I think I'm just gonna fix it straight up lol

#

(that thing to "keep" the issue would actually make it worse, cuz it would also apply to alignment now!)

wary knot
#

Ok there we go, free bug fix with this PR 😂

wary knot
#

ok so

#

@frozen oyster bad news

#

😂

#

my PR makes table a wee bit slower :^)

#

ive been conducting some totally unscientific tests , and it seems to (on release mode) make a document with just a ton of tables and grids about 5-10% slower

#

kinda sadge

#

the cool thing about this is

#

i produced a 1 GB PDF

#

🚀

#

i guess 10k tables with 5k cells each is quite a lot

#

who could tell

vernal herald
#

That's a good chunk of performance taken.
🤔

But this was maybe bound to happen.

wary knot
#

well... Im not sure if theres much we can do

#

theres an extra for loop across all cells

#

i guess that may be relevant

#

i wonder if i can just slap an EcoVec and magically improve performance

#

lol

wary knot
#

they arent just Content

grand haven
vernal herald
#

🤷‍♂️ I'm also of the opinion that one should not overload a PR unnecessarily.. But it is still surprising. Unless you're benchmarking against main, and main have been trimmed to the brim, so maybe the absolute numbers are negligible.. 😅

wary knot
#

in fact i think i have that stat

#

on April 21 (so much before the latest perf improvements by Dherse) I did 200 tables with 200 cells (so a much tamer example probably) and tablex took like idk several minutes

#

here im doing 5000 tables and 5000 grids , each with 5000 cells

#

and it's taking like 38 seconds

#

i kinda wanna compare now lol

#

for science......

#

though for the comparison to be 100% fair we'd need repeatable headers too

#

oh...

#

EcoVec is immutable 😔

#

well.. unless I just .map() ...

#

nah , cant take ownership of its items

#

dream ruined 😂

#

i mean you can but you have to clone it so yeah

#

wait...

#

uhhhhhhhh

#

dumby dumb moment rn? lol

#

idk how i let this slide 😂

vernal herald
#

Is it that you're collecting without needing to do so?

wary knot
#

yes

#

into a Vec even

vernal herald
#

Sometimes you guys are smarter than the rest of us. You should try and help us out a little... 😅

wary knot
#

sorry, just meant to share the realization moment lol

vernal herald
#

But yes that would trim performance nicely. But I wouldn't do more than light trimming. Leave something for code review.

wary knot
#

i gave EcoVec a shot

#

i dont think it will clone cuz theres only a single reference

#

but let's see

#

aw

#

37.9s on the 5000 tables and 5000 grids with 5000 cells

#

so not much

#

(still within a reasonable error margin so probs didnt change at all)

vernal herald
#

That's.. That's so many tables.......

wary knot
#

indeed 😂

#

gotta test this thing to the max

#

im relatively sure that'd have taken like over 30 minutes with tablex though

#

i'll test it on lunch

vernal herald
wary knot
#

they are one and the same now

#

👍

vernal herald
#

Meh in that.. There aren't really that prominent world case for tables that would be the ideal benchmark.

wary knot
#

best i can do is have fun with 5000 tables and 5000 grids 😂

#

it generates a 1 GB PDF

#

isnt that cool

#

ok so

#

ignoring the EcoVec thing, just removing the collect seems to have improved perf by a small bit

#

nice

#

by like 1% idk

#

lol

#

(let's be real: doesnt matter)

#

lol

#

yeah 1% is too generous. more like 0.2% at most

#

anyway but still a logical thing to do so i'll push it

#

lol

#

too bad i dont have more than 64 GB RAM

#

😂

vernal herald
#

Alright... So all of these performance questions should be stopped for now. science

grand haven
wary knot
wary knot
#

If anything this is more of a performance improvement

#

When we compare to tablex usage

frozen oyster
# wary knot they arent just Content

Make sure to strongly type them in your table that might help, also I can always do a quick one over when you’re done for « low hanging fruits » so to speak

#

I wouldn’t worry about perfs too much for now, focus on having the features you want and we can work on that later, gradients could also do with some performance bumps

wary knot
#

Yeah fair enough lol

#

I made too much of a case

#

It’s really not that bad

#

Im testing very unrealistic cases too 😂

#

Oh noes it will now take 4 more seconds to compile their 5000 tables and 5000 grids with 5000 cells each

#

…and +inf less seconds compared to tablex

wary knot
mild pagoda
#

Mysterious, yes

wary knot
#

next PR is already cooking 🧑‍🍳 btw

#

😄

#

at least, I got the insight I needed

#

next PR will be about table.cell(x: 5, y: 999)

#

cant wait to crash typst with y: 999999999999999

#

anyway, nice, it didnt explode the current examples so that's a good sign

vernal herald
#

My boy typstguy is tough @wary knot !

wary knot
#

it shall not be taken down by a simple table

#

just missing a few edge cases now but we're almost there 🚀

bold bluff
#

So is the idea to integrate better base table stuff into typst.exe or to just make a included library for table stuff. Or something completely different?

wary knot
#

everything will become available straight away

#

in typst.exe itself

bold bluff
#

So like tablex but stronger faster better.

wary knot
#

yes

#

I'm basically bringing tablex stuff (which I made in pure Typst) to native tables (in Rust)

vernal herald
#

it does take more effort to write the rust tablex as @wary knot is doing right now...

wary knot
#

in Rust im being very cautious to not make things excessively slow

bold bluff
#

So exciting. So it can detect table breaks and such

wary knot
#

yeah but that will be mostly useful for repeatable headers and such

#

you wont be able to control that manually otherwise

bold bluff
#

So I can repeat headers on like large tables! Thank you!

wary knot
#

Soon™️ 😉

vernal herald
#

@wary knot


[src/main.rs:3] std::mem::size_of_val::<[Result<(), ()>; 2]>(&[Ok(()), Ok(())]) = 2
[src/main.rs:3] std::mem::size_of_val(&vec![Ok :: < (), () > (()), Ok(())]) = 24
[src/main.rs:3] std::mem::size_of_val(&vec![Ok :: < (), () > (()), Ok(()), Ok(()), Ok(())]) = 24
[src/main.rs:3] std::mem::size_of_val(&vec![(), ()]) = 24
[src/main.rs:3] std::mem::size_of_val(&[(), ()]) = 0
wary knot
#

makes sense

vernal herald
#

that's why it didn't do anything, I think. you were collecting () which is a ZST, and there were probably some optimisations there..

wary knot
#

yea fair enough, maybe it detected that it was unnecessary

#

but the vec part wouldnt change anyways

#

cuz vecs are just (ptr, len, capacity) so it's always 24 for 64-bit archs

#

anyway, for my next PR that code has been vastly changed so yeah lol

vernal herald
wary knot
#

yep

#

whatever it was, ive made it worse now 😂

#

cuz for arbitrary positioning you have to build the vector from scratch

#

of course, Vec::with_capacity(cells.len()) helps avoid a crap ton of allocations for the general case (no arbitrary positioning)

#

theres also the fact that technically it's a two-pass algorithm, cuz after you expand the grid, there might be empty, unoccupied cells

so I have to go back and occupy them with empty content

#

but that's only necessary when the grid was expanded, if it wasnt then there should be no way there will be an unoccupied position (you cant place two cells at the same spot!)

#

so we're safe for the general case 👍

vernal herald
#

🦆

wary knot
#

We did it bois

#

The Arbitrary cell positioning is real

vernal herald
#

I'm a duck/goose.

wary knot
vernal herald
#

Dherse calls me a goose, and you seem to use me as a rubber-debugging-duck, so duck/goose.

wary knot
#

eh

#

more like I use this entire thread as a "rubber-debugging-duck"

#

😂

vernal herald
#

I gotta subscribe to the tracking issue at this point...

wary knot
#

thanks 🚀

#

this is the byproduct of the latest work ™️

#

code:

#
---
#grid(
  columns: 3,
  rows: 1.5em,
  inset: 5pt,
  fill: (x, y) => if (x, y) == (0, 0) { blue } else if (x, y) == (2, 3) { red } else { green },
  [A],
  grid.cell(x: 2, y: 3)[B]
)

#table(
  columns: (3em, 1em, 3em),
  rows: 1.5em,
  inset: (top: 0pt, bottom: 0pt, rest: 5pt),
  fill: (x, y) => if (x, y) == (0, 0) { blue } else if (x, y) == (2, 3) { red } else { green },
  align: (x, y) => (left, center, right).at(x),
  [A],
  table.cell(x: 2, y: 3)[B]
)

---
#grid(
  columns: 3,
  rows: 1.5em,
  inset: 5pt,
  fill: (x, y) => if (x, y) == (0, 0) { blue } else if (x, y) == (2, 3) { red } else { green },
  [A], grid.cell(y: 1)[B], [C], grid.cell(y: 1)[D], [E],
  grid.cell(y: 2)[F], grid.cell(x: 0)[G], grid.cell(x: 0)[H],
  grid.cell(x: 1)[I]
)
vernal herald
wary knot
#

it's not in that PR

#

it will be in another PR

vernal herald
#

❤️

wary knot
#

i mean i could make them the same PR but the first PR is big enough as it is

#

and the first PR is more foundational anyway, the second just builds on top of it

#

i think ill just create a draft PR now

#

theres one design decision I still need to make

#

and for that I will have to ask people here

#

😄

#

cuz who doesnt love discussing things

vernal herald
#

with tables, it is very obvious that a lot of voices are not.. hmm.. what's a nice way to say it?
It is a lot of chatter, not enough chowder.

wary knot
#

basically right now this will yield an error

#table(
  columns: 3,
  [A], [B], [C],
  table.cell(x: 2, y: 0)[oh no! conflict!]
)

which is fair enough

but this will also error:

#table(
  columns: 3,
  table.cell(x: 2, y: 0)[oh no! conflict?]
  [A], [B], [C],
)
#

so Q: should cells coming after arbitrarily positioned cells just skip already occupied cells?

#

note that table.cell(x: 2)[This avoids conflict!] would already be placed in the first free row (if specified at the bottom)

#

so could be consistent

#

in tablex this works entirely differently btw, so that doesnt become much of a concern rlly (though it's a bit messy so I think I prefer this way)

vernal herald
#

err noob question, how does rows know how many rows there needs to be?

wary knot
#

basically just divide # cells by # columns and take the ceil

vernal herald
#

jesus christ there is a spec! brb, gotta read 14 pages..

wary knot
#

:p

#

the ideas section is particularly long

#

the rest is pretty short

wary knot
#

theres one little bug still but thats enough for today 😂

wary knot
#

whoops

#

fixing now 👍

wary knot
#

Okay guys

#

Fellow rustaceans, bear with me

#

I've been considering refactoring the first PR (for per-cell customization)

#

right now, the way I implemented this is:

  • instead of using a vector of simple Content for cells, you have a vector of T, where T: Cell and Cell has some useful methods (for now just fill() to return per-cell fill, and also the cell must implement Layout aka must be possible to lay it out in the document, a very basic need)

    • This makes all the table layout code generic. Laurenz was concerned that this could cause some bloat, which is why I am reconsidering this.
  • since cells must be aware of their final properties in the grid, I've also created the CellGrid type, which prepares the vector of cells before the actual layout code even runs.

    • In this sense, GridCell and TableCell, the two elements representing grid cells and table cells respectively, implement ResolvableCell: the CellGrid calls, for each cell, .resolve_cell(x, y, final properties) and the cell adjusts its fields (e.g. it.fill = final fill, it.align = final align etc.) so you can use those properties in show rules
  • in the end, the layout code uses CellGrid instead of a vector of T: Cell, but it's still generic cuz CellGrid itself is generic

#

Now, this works, but it's a lot of generic which I think we can avoid

#

I was considering "abusing" (actually properly using :p) the fact that elements can be .pack()ed into a type-erased Content, and instead of a Cell trait we'd have a Cell type which holds { body: Content, fill: type of fill goes here }

#

And then no more generics (well, maybe before resolving the cell but that's it), and also (implementation detail alert) it turns out we were already calling .pack() when the cell was actually laid out to trigger show rules so... we only have to call it once now!!

#

Perhaps this is a saner approach then 🤔

#

If anyone has any opinions on this, feel free to share, though I think this might work

grand haven
#

Guys, it's Christmas eve. Chill 😂

wary knot
#

Sorry 😅

#

I'll be 100% honest

#

I had this insight while taking a shower and I just had to share it 😂

#

Tables are consuming my brain 😔

random mica
#

Can you elaborate on what being generic means here? Does it mean that two copies of the code are generated once for tables and once for grids?

wary knot
#

yep

#

and also (implementation detail alert) for lists and enums

#

(they are secretly stealing grid's code)

random mica
wary knot
#

very likely so, yeah

mild pagoda
#

Smh, thieves

wary knot
#

ikr

random mica
#

true

wary knot
#

seems like my idea works

wary knot
#

seems like it is slightly faster too as a bonus

#

pretty cool

#

big day for table users

#

😂

random mica
#

sped

wary knot
#

yeah that's always nice

#

I think it's because, with the old design, each cell was being cloned each time it was measured or laid out

#

that does not spark joy

#

meanwhile, tablex probably clones like on every single function call so 😂

keen crescent
#

I haven't looked at the planning doc for a hot minute now, I'm kinda scared to see what all has happened

wary knot
#

You could take a look at the commits in the repo instead if that’s more helpful

#

Most importantly, we began the implementation phase for the first few topics so that’s what most of the discussion here is related to now

#

After item 3 (merging cells) we’ll probably go back to discussing stuff

#

I’m still studying how to implement merging cells though, it’ll definitely be a challenge haha

#

Mostly because on tablex i sidestepped the problem of cells spanning multiple pages by having no cells span multiple pages

wary knot
#

ok guys

#

I found this cool new toy

#

so it must now be used

#

out of pure boredom this interesting and informational diagram was made.

#

which basically sums up the two PRs which are currently live right now

#

actually I'll add the PR #s too hold on

#

there we go

#

.... a bit small but anyway

vernal herald
#

Neat!

frozen oyster
#

Definitely should be a blog post like "converting your tablex tables to the new table API" @robust viper

wary knot
#

Not that bad of an idea tbh 😂

#

There's much more to come though 👍

upbeat pine
#

It will probably take a lot of time, but also can make you think clearly since you will have to explain everything (some stuff).

wary knot
#

too bad they arent wasm yet, would be cool to use this in typst lol

#

but ill document the source here if that ends up being useful for someone

#
direction: right
percell: {
  label: "Per-cell customization"
  Tablex: {
    Cellx
    map-cells
    properties: {label: "properties (fill, align, inset)"}
    position: {label: "cell positioning (x, y fields)"}
  }

  Typst: {
    grid-table-cell: {label: "grid.cell, table.cell"}
    Show rules
    properties: {label: "properties (fill, align, inset)"}
    position: {label: "cell positioning (x, y fields)"}
  }

  Tablex.Cellx -> Typst.grid-table-cell: PR \#3037
  Tablex.map-cells -> Typst.Show rules: PR \#3037
  Tablex.properties -> Typst.properties: PR \#3037
  Tablex.position -> Typst.position: PR \#3050
}
#

(it's pretty simple lol)

keen crescent
vernal herald
wary knot
#

So, I believe tabularray has a solution for the auto / fr column problem

#

added this to the planning document

#

basically, when a merged cell spans both an auto column and a fractional-length column in tablex, it leads to some weird visual glitches where the auto column will expand the most it can to fit the merged cell, and thus the fractional column will have a size of 0pt, even if there are other things in it

#

so a possible solution to that is to allow table users to define whether or not a merged cell (or even all merged cells) can cause auto columns to expand

#

that way, if it looks ugly, you can just disable that

#

this is what tabularray does

#

so yea I think we could adopt something similar

#

otherwise we'd need to do some funky stuff to try to determine what would look best and stuff, when we can just push the decision to the end user

wary knot
#

In regard to merged cells, I'm still planning out the implementation

#

I found out that, apparently, rows with fixed size cannot be broken apart across pages, if I understood correctly

#

Well, worth noting, first of all, that colspans are very simple to deal with, just rowspans are harder

#

So, assuming the simplest case of a rowspan spanning multiple fixed-size rows, simply naively using the existing code would force the entire rowspan to be in a single page

#

(Which is how tablex does it anyway)

#

That's not 100% desirable - it could be possible to allow such rows to be distributed over multiple pages - so the underlying implementation could need some possibly major changes to accommodate for that case

mild pagoda
#

That sounds like the block breaking thing that has been rejected

wary knot
#

You mean the "SplitElem" idea?

mild pagoda
#

I think so

wary knot
#

well

#

in the internal code we dont need it since we can control when to issue a pagebreak

#

so we definitely have more tools at hand than tablex here

#

that's probably one of the major reasons why tablex probably won't be able to properly implement breakable cells

#

OTOH (for native tables) cells in auto rows can be broken across multiple pages so theres that

#

worth noting that this causes the whole row to be expanded across pages, not just a single cell
(which makes sense of course)

#

but for merged cells spanning one or moreauto rows, we'd probably just want the last auto row to expand (to make our own lives easier)

#

although that's a bit harder to do when the auto row isn't the last row, e.g. it spans

1em, 2em, auto, 3em <--- we have to somehow make sure that we expand by "excess space - 3em"

#

(mostly just brainstorming here btw. not proposing anything yet)

#

so i think what we are seeing here is that merged cells would probably be implemented like, separately from rows in general

#

they'd have their own "pagebreaking logic"

#

ill probably play around with blocks and see how they behave to get a better grasp of how things should work

#

well this already gives me a bit of insight on the 100% fixed rows thing

#

we can basically use the same logic as a block here

#

so if it pagebreaks somewhere in the middle, content inside the rowspan will reflow automatically

#

(tablex uses a box but same thing here)

#

though it's kinda weird cuz internally we wouldn't know yet whether or not a pagebreak will occur, since we are the ones creating pagebreaks
so theres that...

#

maybe we can cheat and just literally place a block 😂

#

anyway lol, dont mind me, just thinking out loud

wary knot
#

ok so

#

I've thought of two potential designs for merged cells (w.r.t. rowspans)

#

one of them isnt very clear and is likely more difficult to execute

#

the first potential design is inspired by tablex, but its behavior might not be fully desirable, although it's easier to implement

#

the idea is: if a cell spans multiple rows, the rows must stay in the same page, except (perhaps) the last auto row it spans

#

the last auto row can expand so we can allow the cell to cross multiple pages that way. But , in principle, if it doesnt span any auto rows, then it is restricted to a single page

#

(scary 👻 implementation details alert) internally, this could probably be implemented using the "row group" approach by tablex: all rows in a rowspan are grouped together and laid out together.
Internally, there is a buffer of rows that are ready to be laid out in the current page; when the current page is done (.finish_region is called), the buffer is emptied, and each pending row is "drawn" in the page.
The idea here is that we'd keep a separate buffer. Before sending rows to the "draw in this page" buffer, we send them to a "draw with this row group" buffer first. That buffer is only sent to the "draw in this page" buffer when all rows in the row group are ready to be drawn*. Thus, they will all be drawn in the same page.

*that changes when auto rows are involved, so right now im not considering them.

#

That would at least remain consistent with the current behavior for fixed height rows (they forbid cells from breaking across pages).
But it would need some adaptations to make auto rows work, cuz they can force a page break...
maybe we could force non-last auto rows in a rowspan to not produce any page breaks, but I dont really like that idea

#

either way, assuming all rows are fixed height except for the expandable auto row for that rowspan, we'd basically delay laying out the rowspan cell until the last auto row which allows it to expand, and then it'd be laid out together with that row, thus allowing it to be broken across pages

#

but there are several potential points of failure in this logic so this is just a draft

wary knot
#

in particular rows wouldnt react to rowspans at all that way so... expanding the last auto row wouldnt work

#

well, maybe it could work somewhat using measure beforehand

#

¯_(ツ)_/¯

#

i will have to give it some thought

#

but anyway

#

just wanted to dump here the results from my latest brainstorm

#

😂

wary knot
#

Let's keep going 😎

#

Part 2b should be ready for review by next week 🚀

#

(it's pretty much ready but I want to polish the code and add docs)

vernal herald
#

Those are dherse numbers... 2024 is the year of typst I guess. 😅

wary knot
#

Hehe, I worked on Parts 2a and 2b pretty much in parallel, which let me do this 🙂

#

I admit I'm kinda stuck on Part 3 right now though...

#

(You can see what I mean by scrolling up a bit 😂 )

vernal herald
#

(sorry my brain is a cookie right now, so i'mma just compliment the progress and stuff...) 🚀🚀

wary knot
#

Yeah dw about it

#

Let us handle the big fish 😂

wary knot
#

fellow Rustaceans, what do you think is better? 🤔

#

first one seems simpler overall, but adding 1 with each get does look kinda bad 😂

mild pagoda
#

The hints make it really hard to read

wary knot
#

oof sorry

#
// Let's find the first available position starting from the
// automatic position counter, searching in row-major order.
let mut resolved_index = *auto_index;
while let Some(Some(_)) = resolved_cells.get(resolved_index) {
    // Skip any non-absent cell positions (`Some(None)`) to
    // determine where this cell will be placed. An out of bounds
    // position (thus `None`) is also a valid new position (only
    // requires expanding the vector).
    resolved_index += 1;
}

// or
let resolved_index = resolved_cells.iter()
    .enumerate()
    .skip(*auto_index)
    .find(|(_, cell)| cell.is_none())
    .map(|(i, _)| i)
    .unwrap_or_else(|| resolved_cells.len());
#

erm. just noticed the second comment is kinda wrong there (non-absent would be Some(Some(...))), but anyway... :p

#

tbh the second one does say exactly what it's doing though so it's probably overall clearer

mild pagoda
#

I think there's a method that returns an for find or something like that

sly sparrow
#

position

mild pagoda
#

Means you skip the map find and enumerate

wary knot
#

hmm.. interesting

sly sparrow
wary knot
#

i'd still have to sum *auto_index at the end though

#

since then it'd be the position after the skip

mild pagoda
#

Hmm

wary knot
#

but that's probs not that bad

#

just a map

#

lol. i should probably test later what happens if you specify like a really big number

#

cant wait to crash Typst with huge tables

#

ok, I think I'll settle on this now (tests are passing tm):

// Let's find the first available position starting from the
// automatic position counter, searching in row-major order.
// We have to sum 'next_auto_index' at the end since we'd have
// the position after the initial skip, not from the start of the
// vector.
let next_auto_index = *auto_index;
let resolved_index = resolved_cells
    .iter()
    .skip(next_auto_index)
    .position(Option::is_none)
    .map(|i| i + next_auto_index)
    .unwrap_or_else(|| resolved_cells.len());
#

thanks to both 🤝

wary knot
#

Hey everyone

#

So basically Part 2b (x and y fields for cells) is mostly ready (the implementation works and stuff, needs reviewing of course but otherwise it's fully functional)

Though I'm missing a bit on documentation still
Would anyone be interested in giving me a hand? 👀

#

basically just need to come up with some examples of placing cells arbitrarily (at least one cell with x: something, y: something; one cell with just x: something, which places it in the first row with that column available; and one cell with just y: something, which places it in the first available column in that row)

#

e.g. #table(table.cell(x: something, y: something)[a]), #grid(grid.cell(x: something, y: something)[a])

#

(I can probably do it myself, just wanted to see if anyone could help speed that up :p)

wary knot
#

oooh thats a good one

#

not sure about the ones with "partial" positioning

#

but maybe it could be integrated with that example

#

either way make sure to send more ideas

grand haven
#

How does that even work? It would depend on the order you specify them in right?

wary knot
#

yeah

#

well, thats already how it works technically

#

:p

#

it would just allow you to pick one but not the other coordinate

#

and then it'd place on the first available position

#

if you try to place on a row which is already full, it errors

#

but for a column it always succeeds cuz u can just create more rows

grand haven
#

I can't immediately think of a good real-world example. Some sort of scheduling maybe?

wary knot
#

hmmm

#

actually not bad at all lol

#

maybe you want to do something every saturday

#

so you just fill the saturday column

#

well

#

funnily enough this is more general, it allows you to specify columns without array.zip

#

i didnt think about that

#

anyway. a calendar could work

#

and maybe theres this specific week during which you will be traveling so you fill it with the "travel" event

#

lol

#

(row filling)

grand haven
#

I haven't actually tried. Is it possible to make a cell span both multiple rows and columns at the same time @wary knot ?

wary knot
#

in tablex?

#

yeah

grand haven
#

I now see that it's explicitly mentioned

#

Though something like spanx(2,2)[Hello] might be more user friendly?

#

Over rowspanx(2,colspanx(2)[Hello])

wary knot
#

well, you can write cellx(colspan: 2, rowspan: 2)[Hello]

#

I guess it could be reduced to columns: and rows: or something

#

on native tables

#

but that'd mostly be bikeshedding

#

:p

#

which can definitely be done (just wont be my focus atm)

grand haven
#

Sure, my main question was whether it was possible in the first place.

wary knot
#

yea fair enough

#

to be clear though rn Part 3 (merged cells) is currently kinda blocked cuz im still not sure how im gonna implement rowspans

#

colspans are dead easy to implement though

#

but rowspans involve pagebreaking and everything gets harder :p

wary knot
#

I was thinking that rowspans could have their layouting delayed until we absolutely need to lay it out

if it spans an auto row, that moment will be when we lay out its last auto row.
but if it doesn't, then that moment could be just when we lay out its very last row.

the idea is that, by the moment we reach the last auto row, we already know the sizes of all other rows it spans, so we can just have
last auto row height = max(other cells in the auto row, rowspan content - known row sizes)
known row sizes would exclude fractional rows in later pages since we cant know their actual values until we get to those pages

#

so we would just layout the rowspan using rowspan content - known row sizes as a height, or sum of all row sizes if it doesnt span an auto row

and then we'd backtrack and update each "already finished" page to include the rowspan's contents

#

not sure if that will work but at least I have some idea that I can experiment with now

keen crescent
keen crescent
#

Another idea for some part of it could be a diagram of an n-way cache assignment or the solution to a word-search

#

Maybe a wordle solution?

wary knot
#

I made the first example right now

#

how's this looking? 👀

keen crescent
#

I can mock something up when I get off this bus if you want me to, but I think it might be a little clearer if the calendar grid used just thin/dotted lines on a white background, then the weekday bar was grey

#

Maybe if you wanted to draw more attention to the specific cells you've modified, you could color them to stand out

upbeat pine
# wary knot how's this looking? 👀

If you are asking for a general review. It looks good, but the first thing that I dislike is the unbordered top row, I think there should be some separation between cells. What are you looking for? I don't know what to say. It's not bad, it's just normal.
Nevermind, I don't think this is a general question.

wary knot
#

well, just wanted an opinion on the example

#

from what Mirlo is saying I can see that the cell fills are a bit too dark

#

they stand out more than they should

#

at least

#

also i cant really add a border specific to the top row yet cuz no line customization yet

#

:p

#

but we will get there

#

@keen crescent is this what you were thinking of?

keen crescent
wary knot
#

yeah I agree

#

thanks for the feedback!

keen crescent
#

One more thing might be to make the numbers black and the write-ins red, just to make them stand out more

wary knot
#

hmm idk

#

i think i prefer the other way

#

so

#

this is the code , for the curious:

#
#set page(width: auto)
#show grid.cell: it => {
  if it.y == 0 {
    // The first row is the header of the calendar.
    // We make it bold.
    strong(it)
  } else {
    // For the second row and beyond, we will write the day number for each
    // cell.

    // In general, a cell's index is given by cell.x + columns * cell.y.
    // Days start in the second grid row, so we subtract 1 row.
    // But the first day is day 1, not day 0, so we add 1.
    let day = it.x + 7 * (it.y - 1) + 1
    if day <= 31 {
      // Place the day's number at the top left of the cell.
      // Only if the day is valid for this month (not 32 or higher).
      place(top + left, dx: 2pt, dy: 2pt, text(8pt, red.darken(40%))[#day])
    }
    it
  }
}

#grid(
  // First row (the calendar header) has a gray background.
  fill: (x, y) => if y == 0 { gray.lighten(50%) },
  // Make each day (second row onwards) a square
  // (row height: 30pt, column width: 30pt).
  columns: (30pt,) * 7,
  rows: (auto, 30pt),
  // Events will be written at the bottom of each day square.
  align: bottom,
  // Add some internal padding in each cell.
  inset: 5pt,
  stroke: (thickness: 0.5pt, dash: "densely-dotted"),

  // Header row with each weekday.
  [Sun], [Mon], [Tue], [Wed], [Thu], [Fri], [Sat],

  // This event will occur on the first Friday (sixth column).
  grid.cell(x: 5)[Call],

  // This event will occur every Monday (second column).
  // We have to repeat it 5 times so it occurs every week.
  ..(grid.cell(x: 1)[Meet],) * 5,

  // This event will occur at day 19.
  grid.cell(x: 4, y: 3)[Talk],

  // These events will occur at the second week, where available.
  grid.cell(y: 2)[Chat],
  grid.cell(y: 2)[Walk],
)
#

a bit large mostly because of the comments

#

:p

#

but yeah

#

eh i think ill remove most comments

#

after all the output will be right there

#

so should be easier to associate one thing to another

keen crescent
wary knot
#

oooh

#

yeah it can

#

i mean

#

well except for lines

#

I think this might be good enough perhaps

keen crescent
#

Yeah I think that works

wary knot
#

huzzah 👍

#

thanks , looks much better now i think

#

a truly professional example now

#

people will be making calendar apps in Typst

#

😂

#

i wanna give the chessboard idea a shot as well

#

though im not 100% sure

#

maybe a small chess puzzle works

wary knot
#

well, i made this but it's a bit silly lol

#

apparently the black pawn character doubles as an emoji, leading to that

#

so lol

upbeat pine
wary knot
#

i dont think that looks very nice as an official example though

#

idk

#

the calendar one was just too good

#

😂

#

im failing to think of anything remotely as good

grand haven
wary knot
#

probs

#

i dont think I can do that for compiler examples though :p

#

so I'll probably have to think of something else

grand haven
wary knot
#

theres that game , Go

#

i know nothing about it but maybe we can use it

#

lol

grand haven
# wary knot oh i know

Can you not use a different font by the way? Or do you always run into the emoji issue

wary knot
#

i think it's about PNG export, in PDF it'd probably look normal

#

also

#

theres like

#

a modifier you can add to emoji to force them to display as text

#

but when i add it in Typst it just disappears lol

grand haven
wary knot
#

it's probably somehow lacking font support, idk

#

doesnt sound trivial to fix

#

oh

#

actually theres an issue already

#

lol

#

whatevs

#

ill probably add some random example for #table and link to grids cuz they will have the cool calendar example

#

or maybe I can just copy-paste the calendar example but s/grid/table

#

lol

wary knot
# wary knot well, i made this but it's a bit silly lol

for posterity, here's the code for this chess board thing

#set page(width: auto)
#show table.cell: set text(15pt)

#table(
  fill: (x, y) => if calc.odd(x + y) { aqua.darken(60%) } else { aqua.darken(10%) },
  columns: (auto,) * 8,
  rows: 15pt,
  align: center + horizon,
  inset: 0pt,
  stroke: none,

  [♜], [♞], [♝], [♛], [♚], [♝], [♞], [♜],
  [♟], [♟], [♟], [♟], [], [♟], [♟], [♟],
  table.cell(x: 4, y: 3)[♟],

  ..(table.cell(y: 6)[♙],) * 8,
  ..([♖], [♘], [♗], [♕], [♔], [♗], [♘], [♖]).map(table.cell.with(y: 7)),
)
grand haven
#

I just did this in the web app, and it looks alright even when exported to png @wary knot

#

?r #set page("a7") #array.range(9812,9824).map(it => eval("\\u{"+str(it,base: 16)+"}",mode:"markup"))

grand haven