#Tables
1 messages · Page 2 of 1
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
Reminds me of https://ctan.org/pkg/nicematrix?lang=en
Yes, its a table / matrix typesetting library built on top of pgf/tikz
lol
someone mentioned that package recently in tablex issues
thought it was only for matrices
Ludicrously slow, so I've never really used it. But it does produce nice output
I think that is the primary use, but it also does regular tables.
I can't see any reason to use it over tabularray for actual tables though....
lol
guess I'll just #[allow] it for now
can probably refactor during reviews if needed
baby steps 🙂

right now im here
gotta start to walk before running

the difference between table and grid code is really small
i'm just copying stuff around mostly
well, and refactoring
actually today
pub enum Celled<T> {
Ah, yes — "Celled". XD
The things we do to have no naming conflicts...
(Then the package's name check out.)
kek
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
I cannot believe that someone actually did this reaction, haha! :thinkythonk:
Well that was... Blazingly fast! ||ThePrimeagen reference||
indeed.
though reviews and stuff exist still so yea
:p
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
It's always a burden to recreate the default behavior if you want to fundamentally change a show rule for some element. Right now you can only look at Rust code and try recreating from it in Typst.
Maybe leave how it is with table in v0.10? It's really hard for me to choose, at least now, while I'm busy.
v0.10.0 tables dont have cell show rules
so we are deciding that for the first time :p
No, I mean table show rule. If the align is applied before, then in per-cell it should be the same, I think.
When you do #set table(align: center) does it align before or after table show rule?
neither because theres no show rule here
youre just setting the align parameter for tables
for tables which dont pick it at least
It's suddenly become too hard, I lost ability to think.
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
I'll go back to this discussion later (today, for me).
yeah sure
gonna ping @robust viper just in case
well ive done enough for today 😂
ive begun work on per-cell customization but it's very early still
Also my point there regarding diagbox is kinda moot cuz you can customize the inset outside of the show rule so that can be “ignored”
Pc is off now otherwise I’d update it again
Did we discuss rotated cells? Because I'm currently dying trying to make a table look fine, let alone perfect.
Rotating contents inside cells and having them have the appropriate size?
Well, yes? Just a small text cell that need to be rotated -90°.
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)
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))
}
More accurate and general workaround
THANK YOU!!!
This went hell to heaven real fckn quick.
np 👍
I think you could also table.cell(align: cell.align, inset cell.inset, cell.body) in that case, right?
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.
The problem is mostly that it’d be interesting to be able to modify the cell’s properties there in the show rule and change how it is shown based on other properties
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
Yes so
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
If I'd write a cell show rule, I think I would expect the get the raw cell content and not an aligned or inset version.
get [the raw cell content]
Do you mean get through it.body? If so, that actually makes sense
I think it could be technically possible to do that without changing the cell’s body though, but you got me thinking now 😂
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.)
Yeah fair enough
While it’s a bit sad to lose on the flexibility in that regard , it’s probably for the best
Why is flexibility lost?
Otherwise you’d always get some AlignElem with a PadElem or something
cuz you could just completely override alignment and inset through show rules if you wanted, but that’s not necessarily good
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
couldn't you still do that? inset: 0pt is more or less a no-op. and for alignment the inner one wins.
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 😂
Also huzzah everybody
First PR merged, that was so fast 😂
The benefits of doing all this planning are showing
btw @robust viper have you seen the tablex PR in typst/packages?
I hadn't checked the package repository in a while. It's merged now.
Thanks ❤️
If it's not in the tablex manual, it better be there! It's too useful. Although I haven't tried changing alignment for these and other cells.
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
rotatexfunction 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.
fair enough, but it wont need a workaround in the next Typst version anyway
so not sure if it's worth the trouble now
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.
@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)
so, right now it works the following way:
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
I'll go ahead and agree with @robust viper, because I still not 100% understand all the underlining stuff of Rust Typst (small brain).
@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)
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?
i believe it's a bit of an orthogonal problem
this will just affect the cell's fields pretty much
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
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
that's good!
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
would the children fields remain of type Vec<Content> or would they become GridCell and TableCell with an automatic cast from content?
they would become Vec<T>, T: GridCellTraitThing
T defaults to Content
but GridCell, TableCell have an auto cast from Content yes
I mean on the GridElem and TableElem
yeah
(which can't be generic)
my question is what type is here?
I believe it'd work similarly to ListItem here
okay
we'd use some GridCellElem kind of thing which has auto cast from Content
I'd drop the Elem
sure
It's not typically used in the codebase for scoped elements right now
i think GridCell might not be the ideal name for the trait then cuz the conflict is obvious
Why would the trait need to be implemented for Content then?
If it's only ever a GridCell or a TableCell
perhaps Cell?
cuz list, enum use GridLayouter too
Yeah Cell is a good name for the trait
Ah
so for Content it just mocks everything that isnt needed
fair
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
Are you making the layouter generic or &dyn?
for now generic
I'm a bit afraid of code and compile time bloat with generics
The dynamic dispatch overhead would probably be completely irrelevant
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)
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
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
so that the grid layouter has all the state it needs?
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)
sounds good, I also felt like something like that would be needed while I briefly started to implement this a while back
yeah it's just a bit nicer overall in the code organization as well
let's see how this goes 🙂
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
No, don’t worry
The show rule would only be invoked when the cell is about to be laid out
I'm not sure what you mean by that
Fixed
:p
Basically the cell will just lay out its body by default
But the show rule would override that
gotta be the scariest thing to get an error in bibliography.rs after modifying Grid 😂
as long as it's just a compile time error
wait until you get "failed to format citation (this is a bug)" at runtime
im not gonna be waiting for it 😂
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.celloutside of the table or a table'sfill: ...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 removeautoas 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.)
for the curious, my latest attempt is here https://github.com/PgBiel/typst/commit/241a6712b4648199883b7defe0e6db023ffb2bf8
eventually i faced so many borrow checking and mutability problems that I thought "okay, I need to think this differently" lol
Regarding the above: I believe we could use the Synthesize trait for this
so maybe the resolve thing isn't needed after all
please don't use it for anything
I'm currently trying to get rid of it
understood
it was probably for the best tbh 😂
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
I wouldn't worry too much about it
Cloning content is relatively cheap
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
perhaps it would be possible with the style chain, as you brought up before
yeah idk exactly how though 🤔
you would need to construct one Styles object per unique fill-align-inset combination and chain that to the grid's style chain
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?
whenever a grid cell is layouted, it gets passed the grid's styles
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
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?
or on the grid itself (which I believe to be the more common case), yea
what do you mean with "on the grid itself"?
ok
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.
yeah thats the thing
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 😂
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
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?
you need to implement Show and add it the cell's capability list in the elem macro
looking at it, I'm wondering whether grid.cell(align: right)[C] is idiomatic as opposed to align(right)[C]
But I guess it makes sense to have it for consistency
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
I thought that the alignment and inset would be applied in grid.cell's Show rule.
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
only if they write a cell show rule that throws away it
which you just shouldn't do typically
Fair enough
I think the styling rework will help here too
So the user could idk it.x = y maybe
*not box, but rather pad
But it’s not like that’s needed atm
Yeah , though box / block is easier 😂
I’m not sure if pad(..it.inset) works , does it?
ideally, the mutation (if we even allow it) should be last resort and show: set should cover setting fields in an immutable way
it should, I think
hmm. Interesting
Was thinking about the rest: things
But if it supports that then that’s good
Yeah fair
I think I’ll just place it in the Show rule then
maybe it breaks in some cases, maybe rest is positional there
but in Rust code it's definitely the right element
@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
Content implements Layout, but the cell needs to be packed for that impl to be invokable.
You shouldn't apply the show rule yourself, that's the job of content's Layout impl.
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 🤔
okay
it works if i remove the .padded
very odd lol
only happens when using Show instead of Layout as well
ok, it's specifically vertical alignment apparently
though it works with grid.cell(align: horizon)
hmmm
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
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
well > in CSS specifically means direct descendant
indeed
but the non-> version does
perhaps it one day might
maybe
do we have a forge for a selectors overhaul?
though i can imagine it'd be a bit weird to define like what is a direct descendant
well I mean
there is #1175894504146993304
not really the same
it's linked though
well anyway
there's an issue requesting something similar though i think
That's fair, you might want to not count some intermediaries but do count others
yea and show rules make stuff kinda blurry
Yeah I remember that discussion on github
I reckon it deserves its own forge
had something to do with pattern-matching / destructuring, can't find it from my phone though
I think we can have discussion about it in #1175894504146993304 for now.
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
?r ```
#set page(height: 50pt)
#pad[#align(horizon)[Hello]]
The align should be around the pad, not inside of it.
?r ```
#table(
align: top,
rows: 50pt,
align(bottom)[Hello]
)
the outer alignment wins
but if align is not specified, it doesn't happen
?r ```
#table(
rows: 50pt,
align(bottom)[Hello]
)
hmmm
i believe it's still behaving a bit differently in any case
that SimpleTest element did not have any sort of custom alignment
but it was in a pad
and a pad is like a block, it doesn't auto-expand to the full height
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
but what's weird is that here the inner alignment wins
?r ```
#block(height: 50pt, fill: red)[
#align(top)[
#align(bottom)[ok]
]
]
that's somehow inconsistent or I'm missing something
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
?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],
)
same problem on main
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
That should definitely be done
cuz inset doesnt exist atm
as an optimization in any case
you also don't have to use padelem
you can do your own insetting
but we should probably rather fix pad somehow
mayhaps
i think it doesnt hurt to do it on tables like
the current behavior just ignores align so
users probably noticed that
lol
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],
)
ah
that's sneaky
I think it just sort of wins
but the padding doesn't seem to be the problem then
im pretty sure its not doing any alignment as i added a debug print there
either way just not padding works for me
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
🚀
nuuuu
tbh i considered moving fill to Show as well. but im not sure if that'd be desired
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
time to employ references-by-indices trick.
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)
yeah that worked
nice
PR almost ready
@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)
Should be okay
PR is out 🚀 https://github.com/typst/typst/pull/3037
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
You're also implementing it in typst? That's nice.
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 🚀
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
So
it seems I found a relatively simple way to fix https://github.com/typst/typst/issues/1386 ? But I'm not sure if this is how a fix should go
Description When we create a Table with gutter, the number of col and row are wrong. We can test it with a table and add function to fill odd row or odd columns. Reproduction URL https://typst.app/...
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...
Ooooh I see, this issue only applies for fill
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!)
Ok there we go, free bug fix with this PR 😂
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
That's a good chunk of performance taken.
🤔
But this was maybe bound to happen.
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
well also all cells hold state now so
they arent just Content
I mean, it's doing more things. I'm sure it's way faster than tablex
🤷♂️ 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.. 😅
oh yeah without a doubt
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 😂
Is it that you're collecting without needing to do so?
Sometimes you guys are smarter than the rest of us. You should try and help us out a little... 😅
sorry, just meant to share the realization moment lol
But yes that would trim performance nicely. But I wouldn't do more than light trimming. Leave something for code review.
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)
That's.. That's so many tables.......
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
See.. That stat could be interesting to know.
But I like the benchmarking of actual real world cases... Although tables are a bit meh.
I didn't know your thing was also about grids. 😅
Alright i'mma let you be! Awesome progress.
regarding grids: https://github.com/typst/typst/pull/3009
they are one and the same now
👍
Meh in that.. There aren't really that prominent world case for tables that would be the ideal benchmark.
yeah ig
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
yeah
too bad i dont have more than 64 GB RAM
😂
Alright... So all of these performance questions should be stopped for now. 
pagefile and some patience /s
Great idea! I’ll let my computer run for 5 years to test
Yeah… it’s just not comparable at all 😂
If anything this is more of a performance improvement
When we compare to tablex usage
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
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
lol, a mysterious file has appeared
Mysterious, yes
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
My boy
is tough @wary knot !
indeed
it shall not be taken down by a simple table
yayy
just missing a few edge cases now but we're almost there 🚀
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?
So like tablex but stronger faster better.
yes
I'm basically bringing tablex stuff (which I made in pure Typst) to native tables (in Rust)
it does take more effort to write the rust tablex as @wary knot is doing right now...
yeah, that also explains why tablex is so slow
allocations, allocations everywhere
in Rust im being very cautious to not make things excessively slow
So exciting. So it can detect table breaks and such
yeah but that will be mostly useful for repeatable headers and such
you wont be able to control that manually otherwise
So I can repeat headers on like large tables! Thank you!
Soon™️ 😉
@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
makes sense
that's why it didn't do anything, I think. you were collecting () which is a ZST, and there were probably some optimisations there..
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
it's just a funny instance of.. it wasn't that bad.
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 👍
🦆
I'm a duck/goose.
sorry, im a bit too used to that expression 😂
Dherse calls me a goose, and you seem to use me as a rubber-debugging-duck, so duck/goose.
Sorry, it is past midnight here.. I can barely focus. Congratulations!
I gotta subscribe to the tracking issue at this point...
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]
)
Right.. so the images here are design not the implemented stuff... you just managed to implement it https://github.com/typst/typst/pull/3037?
i just implemented this
it's not in that PR
it will be in another PR
❤️
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
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.
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)
err noob question, how does rows know how many rows there needs to be?
basically just divide # cells by # columns and take the ceil
jesus christ there is a spec! brb, gotta read 14 pages..
Adds fields x and y to grid.cell and table.cell, in both ways: not only can you access them in show rules to determine how the cell should be displayed based on its coordinates, but you can also sp...
theres one little bug still but thats enough for today 😂
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, whereT: CellandCellhas some useful methods (for now justfill()to return per-cell fill, and also the cell must implementLayoutaka 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
CellGridtype, which prepares the vector of cells before the actual layout code even runs.- In this sense,
GridCellandTableCell, the two elements representing grid cells and table cells respectively, implementResolvableCell: 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 alignetc.) so you can use those properties in show rules
- In this sense,
-
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
Guys, it's Christmas eve. Chill 😂
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 😔
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?
yep
and also (implementation detail alert) for lists and enums
(they are secretly stealing grid's code)
That explains https://github.com/typst/typst/issues/1050
very likely so, yeah
Smh, thieves
ikr
true
seems like it is slightly faster too as a bonus
pretty cool
big day for table users
😂
sped
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 😂
I haven't looked at the planning doc for a hot minute now, I'm kinda scared to see what all has happened
Not that much honestly, compared to when we were discussing
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
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
Neat!
Definitely should be a blog post like "converting your tablex tables to the new table API" @robust viper
It will probably take a lot of time, but also can make you think clearly since you will have to explain everything (some stuff).
for the curious, this was done using D2 , which I mentioned in #off-topic earlier
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)
Is this D2? Am a stupid
Don't be hard on yourself. Dherse, pgsuper, and a couple others have infinite ability to context flip seamlessly.
It is impressive, dizzing and yeah.
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
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
That sounds like the block breaking thing that has been rejected
You mean the "SplitElem" idea?
I think so
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
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
the second potential design is to lay out all non-merged cells, and then at the end lay out the merged cells on top
this would probably be more "natural" since it wouldnt require rows in a rowspan to stay together in the same page
but im still not sure where i'd go with that
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
😂
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)
Those are dherse numbers... 2024 is the year of typst I guess. 😅
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 😂 )
(sorry my brain is a cookie right now, so i'mma just compliment the progress and stuff...) 🚀🚀
fellow Rustaceans, what do you think is better? 🤔
first one seems simpler overall, but adding 1 with each get does look kinda bad 😂
The hints make it really hard to read
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
I think there's a method that returns an for find or something like that
position
Means you skip the map find and enumerate
hmm.. interesting
A trait for dealing with iterators.
i'd still have to sum *auto_index at the end though
since then it'd be the position after the skip
Hmm
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 🤝
A trait for dealing with iterators.
oop nevermind I was too late
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)
Chessboard maybe?
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
Hm, yeah that's trickier
How does that even work? It would depend on the order you specify them in right?
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
I can't immediately think of a good real-world example. Some sort of scheduling maybe?
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)
I haven't actually tried. Is it possible to make a cell span both multiple rows and columns at the same time @wary knot ?
Yes
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])
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)
Sure, my main question was whether it was possible in the first place.
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
okay. i think i got an initial idea, ill just dump it here for future reference but others shouldnt worry about it
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
This has the advantage of an inherently funny name
You could do a seating chart for a classroom/event; Assign a student to a specific seat, reserve a whole row or column, etc.
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?
thanks for the suggestions 👍
I made the first example right now
how's this looking? 👀
Pretty good, I definitely think it gets the point across. There's a lot going on visually though, which is a little overwhelming
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
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.
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?
Yeah, it's definitely easier to read
One more thing might be to make the numbers black and the write-ins red, just to make them stand out more
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
I made the mock up anyways just because; I don't think this can actually be done at the present time
oooh
yeah it can
i mean
well except for lines
I think this might be good enough perhaps
Yeah I think that works
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
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
Sounds like the next "Christmas Game" spin-off. ;)
lol
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
I think using svgs would be easiest
probs
i dont think I can do that for compiler examples though :p
so I'll probably have to think of something else
Get someone to publish a package that has chess pieces, then use those 🧠
oh i know
theres that game , Go
i know nothing about it but maybe we can use it
lol
Can you not use a different font by the way? Or do you always run into the emoji issue
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
that sounds like something that should be fixed
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
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)),
)
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"))
