#Tables

1 messages Β· Page 6 of 1

wary knot
#

woo

thorny coral
#

Shouldn't the cells be numbered according to their position in their first line? Like so:

 1    2    3
 4         5
 6    7    8
      9   10
11   12   13
14   15   16
     17   18
19   20   21
#

That would make more sense imo, though I don't know how hard it would be to make it work

grand haven
#

The numbering isn't something the user is going to be exposed to, so it doesn't necessarily have to make complete sense.

#

As far as I understand

robust viper
#

It affects counters

#

I think

grand haven
#

Hm yeah

robust viper
#

here

grand haven
#

Like footnotes

#

If it's hard to fix it's not the end of the world if that doesn't happen before 0.12 though?

wary knot
#

I'm afraid it wouldn't get into 0.11 in time , if we decided upon that

#

With that said, i think the behavior I achieved should be at least consistent

#

Though i still haven't decided on what happens at the last row

#

ATM the rowspan content is placed before the last row (but after the previous ones)

#

But it could also be placed after the last row. Idrk

#

I think it doesn't matter in the end so I'll probably not touch it

wary knot
#

Btw, if anyone has rowspan tables they want tested, just ping me πŸ‘

keen crescent
# wary knot Btw, if anyone has rowspan tables they want tested, just ping me πŸ‘
#let kn = 0.002
#let vdd = 5
#let vtn = 1.5
#let voh(r) = calc.round((vdd - (1/(2 * kn * r))), digits: 3)
#let vol(r) = calc.round((calc.sqrt((2 * vdd) / (3 * kn * r))), digits: 3)
#let vih(r) = calc.round((vtn - (1 / (kn * r)) + (1.63 * calc.sqrt(vdd / (kn * r)))), digits: 3)
#let vil(r) = calc.round((vtn + (1 / (kn * r))), digits: 3)
#let nmh(r) = calc.round((voh(r) - vih(r)), digits: 3)
#let nml(r) = calc.round((vil(r) - vol(r)), digits: 3)

1. #block[
  #figure(
    kind: table,
    tablex(
      align:horizon,
      columns: 8,
      rows: 5,
      header-rows: 1,
      repeat-headers: true,
      auto-vlines: false,
      auto-hlines: false,

      map-cells: cell => {
        if cell.y == 0 {
          cell.content = strong(cell.content)
        } else if cell.x > 1 and cell.y > 0 {
          cell.align = right
        }
        if calc.rem(cell.y, 2) == 1 {
          cell.fill = gray.lighten(50%)
        }
        cell
      },

      colspanx(2)[Inverter], (), [$V_("OL")$ (V)], [$"NM"_upright(L)$], [$V_("IL")$ (V)], [$V_("IH")$ (V)], [$"NM"_upright(H)$], [$V_("OH")$ (V)], hlinex(),
      rowspanx(2)[NMOS, $1 upright(k) ohm$ load], [Measured], [1.1], [#calc.round((2.2 - 1.1), digits: 3)], [2.2], [4.1], [#calc.round((4.61 - 4.1), digits: 3)], [4.61],
      (), [Calculated], [#vol(1000)], [#nml(1000)], [#vil(1000)], [#vih(1000)], [#nmh(1000)], [#voh(1000)],
      rowspanx(2)[NMOS, $20 upright(k) ohm$ load], [Measured], [0.19], [#calc.round((1.5 - 0.19), digits: 3)], [1.5], [2.2], [#calc.round((4.78 - 2.2), digits: 3)], [4.78],
      (), [Calculated], [#vol(20000)], [#nml(20000)], [#vil(20000)], [#vih(20000)], [#nmh(20000)], [#voh(20000)],
            ),
    caption: [
      Inverter parameters for the resistor-load inverters
      ]
    )
#

I don't know if that one is any more complex than the one's you've likely been testing

wary knot
#

Ayyy thank you πŸ˜„

#

Don't worry it will be very useful

#

I really just need real-world examples, i think my random tables with a a a a a a a a in them don't cut it πŸ˜‚

keen crescent
#

Ah lol

keen crescent
wary knot
#

yay im back to my linux PC

grand haven
wary knot
#

#contributors message

grand haven
wary knot
#

but i mean i have no idea what could have caused that difference in behavior

#

however they are indeed doing some weird stuf

upbeat pine
#

If the wrong alignment is used, why this is still an issue again?

#

Oh... I didn't understand the second comment.

#

I mean, isn't this a "this is not how things work" case? For me, since no one should've used that, it's totally fine that this change has happened.

robust viper
#

The show rule is pretty cursed, but I still wonder what caused it.

wary knot
#

could perhaps be related to inset

#

or maybe it's just that we have some more element nesting with table.cell everywhere, idk

robust viper
#

the flow won't be able to see the alignment anymore

#

I've closed the issue.

wary knot
#

lol. turns out this was just a bug

#

(cc @bold bluff )

#

the cell has a bottom stroke, but it wasnt being applied

#

cuz this is pretty much an edge case which is when you specify a rowspan cell occupying multiple auto rows all by itself (occupies 100% of columns)

#

visually you only see one row but hlines dont (or didn't) work with visuals

#

much better now

wary knot
#

seems like rebasing to main broke a rowspan test

#

noo

#

fix is in progress . . .

bold bluff
#

Sounds tricky. But it looks pretty good. Perhaps a good debug style would be helpful. Something where all the attributes of the table would be visible just for verification.

wary knot
#

but yea

#

basically what i changed is that the top / bottom borders now fold with the cell strokes, they dont totally override them anymore

#

but the borders always have priority

#

so it's a step forward

#

ideally we'd have stroke: (outside: ...) but this will be enough for now

wary knot
#

the test broke but i think it was for the best

#

before rebasing

#

after rebasing

vernal herald
#

πŸ‘Œ

wary knot
#

aight

#

I wrote the PR description

#

yay

#

it's huge πŸ˜‚

wary knot
#

meanwhile...

#

πŸ€”

vernal herald
wary knot
vernal herald
#

You have to assume, that I just don't see or get things discussed here. 😭

wary knot
#

lol

#

well theres something particularly interesting in the image...

signal breach
#

Repeating headers in multiple page tables! πŸŽ‰

wary knot
#

i made a quick experimental version in like 30 min (building upon the rowspans PR, which has a good chunk of the hard work done)

#

that experimental version is probably enough for most simple cases to be honest lol

#

but non-simple cases are the big sad

wary knot
#

hmm

#

i think the non-simple cases are mostly restricted to auto rows honestly

#

which is basically like, when u have a multi-page auto row, u have to make sure the continuation of the auto row doesnt go over the header

#

and ive mostly tackled this right now in an experimental way

#

well, that works for me!

#

aint no way

#

there has to be something else to do (yes there is I have to make a user-facing API but let's ignore that for now)

#

i mean okay there are N ways i could improve this code

#

but lol

#

it's really that simple

wary knot
#

rowspans exist

#

sec!!!

vernal herald
wary knot
#

i was intentionally cryptic to raise suspense

#

im sorry

#

πŸ˜‚

#

ok epic (no idea why the thing at the bottom does that but anyway)

wary knot
#

ok so

#

should the gutter gutter or what should the gutter gutter gutter

#

(me on the verge of deleting the gutter parameter)

lone nova
wary knot
#

i thought about it

#

but i was afraid of just going back to LaTeX where you have warnings like that everywhere

#

lol

#

i feel like the warning will be the document itself

#

lol

#

and regarding what happens: headers are unbreakable sooo you'll just get a screwed up table

wary knot
#

lol

#

the table seems to magically disappear

lone nova
wary knot
#

probably cuz of my orphan header check

#

skips a region if it would just have a header

#

but i should probably push some empty content / frame instead

#

ok so

#

maybe that's not a good idea

#

or maybe it is and it's your fault that you made a screwed up thing πŸ˜„

#

here's your table!

#

πŸ˜‚

#

lol

#

i think i found the perfect page height

#

it seems to cause an infinite loop

#

of pagebreaks

robust viper
# wary knot lol

how are you even awake? it's like 5am where you are, isn't it?

wary knot
#

maybe

#

or maybe not!

robust viper
#

maybe you've tricked us all and you aren't in Brazil in the first place!

wary knot
#

yeah i sometimes walk all the way to Japan

#

great exercise for the legs!

robust viper
#

it's good to work out

wary knot
#

indeed

#

but yeah i mean

#

table headers are a thing now sort of

#

πŸ˜‚

#

a very simple version but yea

#

all thanks to huge amounts of hard work in the rowspans PR :p

#

first version came out in 30 min

#

without handling any of the extra complexity

#

second version (ensuring auto rows and rowspans recognize headers exist) took two hours after that

#

:p

#

anyway yeah epic

#

now i dont know why the layout engine is in an infinite loop

#

but that's something for another day

wary knot
#

it's so horrible

#

and sad

#

it's in the rowspans PR

#

πŸ˜‚

#

see this is why you make headers at 5 am, you prevent a natural disaster

#

clearly something is off here πŸ˜‚

grand haven
#

Dude, go to sleep πŸ˜‚

wary knot
#

i will but that was too horrible to overlook

#

πŸ˜‚

#

(im advancing the regions variable and checking self.regions... whoops)

#

done (that wasnt the only problem but anyway)

#

the useless example of what not to do lives

#

πŸ‘

#

ok ill sleep now

#

cya

robust viper
#

@wary knot something fishy is happening with this code:

#set page(width: 10cm, height: 6cm, margin: 1cm)
#set text(size: 11pt)
#table(
  columns: (auto, 4cm, 1fr),
  align: center,
  [Hello],
  table.cell(rowspan: 2)[There \ You \ Are!],
  [World],
  [What],
  [Up],
  [Now],
  table.cell(colspan: 2)[Here we go.],
  table.cell(colspan: 3, lorem(10)),
  [Continuing],
  table.cell(rowspan: 3, lorem(8)),
  [Here],
  [A],
  [B],
  [C],
  [D],
)
wary knot
wary knot
#

seems normal to me

#

unless im missing something?

#

agh

#

screenshot tool ate some letters

#

not last auto row

#

lol

#

also I plan on undrafting today btw

#

I already updated the PR description and stuff

#

so you can read that in the meantime if you want to :p

vernal herald
#

Damn!

#

πŸš€πŸš€

robust viper
# wary knot

why is it breaking into three pages when it would fit into two? and why is there space above the lorem(8)?

robust viper
#

I'm about 2/3 through the review.

wary knot
#

oh ok

#

im gonna push a small fix, not sure if you caught that in your review

#

of using the correct pod.full in measure_auto_row

robust viper
wary knot
#

all cases

#

it's always using the full of the auto row's region

#

but if the cell starts in a previous region, that region's has to be used instead

#

and we store that data in the Rowspan struct so it's ok

robust viper
#

ah

robust viper
wary knot
#

yea fair enough

#

just didnt want to conflict with your ongoing review

robust viper
#

I guess GitHub will be able to handle it

wary knot
#

i caught this mostly cuz im reviewing by myself too hehe

#

the more the merrier as they say

robust viper
wary knot
#

im not 100% sure of why there was a pagebreak but in principle it's possible it just triggered the can_skip branch

#

also the margin is rather significant there

#

so that might have had an influence in the space available

#

i cant say i can really explain that behavior fully though but seems like a rather complex interaction of things

robust viper
#

if you replace table.cell(rowspan: 3, lorem(8)) with table.cell(rowspan: 2, lorem(8)) you can see that it should fit pretty easily

#

and then there is the fact that the Lorem ipsum is bottom aligned for some reason

#

it seems to me like it is just placed in a wrong way by the layouter

#

(even with rowspan 2 this weird alignment happens)

wary knot
#

hmmm

robust viper
#

somewhat minimized:

#set page(width: 10cm, height: 2.5cm, margin: 0.5cm)
#set text(size: 11pt)
#table(
  columns: (1fr, 1fr, 1fr),
  [A],
  [B],
  [C],
  [D],
  table.cell(rowspan: 2, lorem(4)),
  [E],
  [F],
  [G],
)
#

does not have the extra break, but still the weird shift

wary knot
#

i cant really explain that right now

#

seems like an artifact from flow in general

#

of note: I use (true, true) region expand when laying out rowspans

#

that is correct, right?

#

(when measuring the auto row it uses self.regions.expand which is usually (true, false) )

#

idk if that could be part of it but worth saying

#

OH

#

I KNOW WHAT IT IS

#

πŸ˜‚

#

ok give me one moment

#

wow

#

it's really subtle πŸ˜‚

#

well

#

at least what the "extra break" problem is

#

one down @robust viper πŸ‘

robust viper
#

I'm now down to the last remaining function to review: simulate_and_measure_rowspans_in_auto_row.

wary knot
#

ok

#

ive pushed the fix above

wary knot
#

it was checking the first region of the rowspan to see if it's empty there but that doesnt matter

#

now it skips frames from previous regions

#

btw @robust viper im not sure if you address that in your review but what are your thoughts on having cell.breakable being resolved on the fly?

#

for one, it's a bit inconsistent with the remaining cell properties, cuz they are all resolved before show rules, while that one property would remain as auto

#

but also i thought that the way Typst resolves breakable would be a bit of a, perhaps, low-level detail

robust viper
#

if you can resolve it up-front, why not

#

if not, then it's also not a big deal

wary knot
#

yeah i can

#

right now i just store it as auto and delay the work

#

but maybe it's not that bad to expose it to the user

#

it being whatever we resolve

#

so i think ill just do it

#

well actually theres a small problem

#

we'd need access to the rows and check gutter and stuff while that stuff is being built

#

sounds like a pain

#

checking it later is simpler cuz we just use grid.rows

#

i think ill keep it as is for now, i dont expect people to be writing show rules with cell.breakable anyway :p

#

actually, i messed with it and it's possible, but intersperse would make it sooo much easier lol

#

to simulate rows + gutter

#

but okay

#

(seems like it's a nightly feature)

robust viper
#
#set page(width: 10cm, height: 7cm, margin: 1cm)
#set text(size: 11pt)
#table(
  columns: (1fr, 1fr, 1fr),
  gutter: 5pt,
  align: center,
  ..upper("abcdef").clusters(),
  table.cell(rowspan: 4, lorem(13)),
  ..upper("abc").clusters(),
  table.cell(rowspan: 2, breakable: false, lorem(10)),
  ..upper("hijklm").clusters()
)
wary knot
#

thanks

#

i'll take a look

robust viper
# wary knot i'll take a look

I found another bug I think:

#set page(width: 10cm, height: 10cm, margin: 1cm)
#set text(size: 11pt)
#table(
  columns: (1fr, 1fr, 1fr),
  align: center,
  rows: (4cm, auto),
  [A], [B], [C],
  table.cell(rowspan: 4, breakable: false, lorem(10)),
  [D],
  table.cell(rowspan: 2, breakable: false, lorem(20)),
  [E],
)
#

somehow the second, large unbreakable rowspan doesn't lead to row group to shift to the new page.

#

tablex handles this case correctly.

wary knot
#

pushed

wary knot
#

basically just replaced lorem(8) with a rectangle

upbeat pine
#

(the tables are cooking so hard right now 🍲)

wary knot
#

ye

#

cuz there are bugz

#

πŸ›

upbeat pine
#

kill 'em! πŸ”« burn them to the ground! πŸ”₯

wary knot
#

im not 100% sure but

#

basically in layout_rowspan there is a dy which is supposed to be the dy of the rowspan in the first region

#

for all further regions it's zero

#

but

#

we zip with the fragment returned by the laid out rowspan

#

so it's possible that there could be a mismatch between first region and first frame

#

or something

#

the problem is somewhere in there, i'll take a further look soon (gonna eat lunch now so brb for a bit)

robust viper
#

I've left my review. It's mostly just stylistic things as finding logic bugs is too hard here. But I have at least roughly understood how everything fits together.

upbeat pine
#

This means that I probably wouldn't understand a thing...great.

wary knot
#

it's definitely not a trivial system

#

but I guess that's what happens when we solve non-trivial problems :p

#

but at least the PR description should have a good overview over what's going on

#

or at least what I considered while writing the code

upbeat pine
#

So, is the today's review and patching final?

wary knot
#

wdym?

upbeat pine
#

Will the PR be merged today?

wary knot
#

depends on multiple factors

upbeat pine
#

Because it looks like it.

wary knot
#

lol

upbeat pine
#

ok

wary knot
#

but hopefully we should be done with it by tomorrow or so

#

if possible today

upbeat pine
#

yeah, It would be cool to have a new release on the first day of spring. Or rather warm.

wary knot
#

march 19?

upbeat pine
#

why 19?

wary knot
#

google told me thats the first day of spring in the northern hemisphere

upbeat pine
#

Sprint starts from March, so it should be March the 1st.

#

In 2 days (more or less).

wary knot
#

no thats not when spring starts

upbeat pine
#

In Russia it is. Because spring is March, April, May. Other 3 seasons have other 9 months. Otherwise it would be too complicated.

wary knot
#

yea but it's not in the first day of march

#

sure you can simplify it to "whole march" if you want but thats not how geography works :p

#

but either way

#

no the release isnt coming on march 1st

#

it's impossible :p

upbeat pine
wary knot
#

well thats a different definition of spring then (not the geographical one but some government-defined thing)

#

cuz spring starts after the equinox

#

which is the day when the day and night are equally long

#

but anyway

#

off-topic lol

upbeat pine
wary knot
#

layout_rowspan seems to be doing OK so far during debugging

#

ive printed the frame items for the fragment and it seems that the cell itself is placing the rectangle there

#

so that's odd

#

of note, this happens when inset is disabled for that particular cell

#

seems like the top inset is growing too much or something? weird

#

but yeah something is definitely fishy

#

(almost) same cell contents

#

adding a single letter at the top makes the rowspan behave properly

night python
wary knot
#

i was thinking of the first one yeah

wary knot
#

but then during a second flow layout run something changes

#

i assume that's the alignment

#

but idk

#

something changes the rectangle's position from 5pt (correct, inset) to 22pt (wat) (aka 2em)

#

the angry little men in my RAM must be very angry

wary knot
#

but if I don't then it gets moved to (5pt, 15pt) instead of the otherwise correct (5pt, 5pt)

#

very odd

wary knot
#

hmmm

#

it seems that theres some invisible something with height 10pt above the rectangle

#

after spending a good amount of time stepping through flow code

#

lol

bold bluff
#

Is there … misplaced gutter effects ?

wary knot
#

i was thinking about it

#

and i think it's inset from the previous page

#

i.e. this invisible thing was supposed to be up here

#

seems to be the only plausible explanation so far

#

cuz it seems to always be 2x inset

#

also btw

#

regarding headers

#

i do indeed think that they wont make it to 0.11 in time

#

my implementation is cool but it's naive

#

if my mental model is correct, it doesnt properly preserve ratios (100%, 50% etc.)

#

so that could be a problem

#

and fixing it would be everything but trivial

#

:p

bold bluff
#

What makes the invisible inset move across page boundaries?

wary knot
#

basically needed to measure with infinite height

#

also added some .max(zero) around cuz apparently one of the rows was being measured with negative height

#

lol

wary knot
#

maybe it somehow couldnt find space in the first page

#

but while i was debugging that didnt seem to be the case (there was 17pt available in the first page and it requested 10pt)

wary knot
#

i wonder if it's some sort of bug in the text flow code, idk

wary knot
#

code:

||

#set page("a5")
#let kn = 0.002
#let vdd = 5
#let vtn = 1.5
#let voh(r) = calc.round((vdd - (1/(2 * kn * r))), digits: 3)
#let vol(r) = calc.round((calc.sqrt((2 * vdd) / (3 * kn * r))), digits: 3)
#let vih(r) = calc.round((vtn - (1 / (kn * r)) + (1.63 * calc.sqrt(vdd / (kn * r)))), digits: 3)
#let vil(r) = calc.round((vtn + (1 / (kn * r))), digits: 3)
#let nmh(r) = calc.round((voh(r) - vih(r)), digits: 3)
#let nml(r) = calc.round((vil(r) - vol(r)), digits: 3)

1. #block[
  #show table.cell: cell => {
    if cell.y == 0 {
      strong(cell)
    } else {
      cell
    }
  }
  #figure(
    kind: table,
    table(
      align: (x, y) => if x > 1 and y > 0 { right } else { horizon },
      columns: 8,
      rows: 5,
      stroke: none,
      fill: (_, y) => if calc.odd(y) { gray.lighten(50%) },

      table.cell(colspan: 2)[Inverter], [$V_("OL")$ (V)], [$"NM"_upright(L)$], [$V_("IL")$ (V)], [$V_("IH")$ (V)], [$"NM"_upright(H)$], [$V_("OH")$ (V)], table.hline(),
      table.cell(rowspan: 2)[NMOS, $1 upright(k) ohm$ load], [Measured], [1.1], [#calc.round((2.2 - 1.1), digits: 3)], [2.2], [4.1], [#calc.round((4.61 - 4.1), digits: 3)], [4.61],
      [Calculated], [#vol(1000)], [#nml(1000)], [#vil(1000)], [#vih(1000)], [#nmh(1000)], [#voh(1000)],
      table.cell(rowspan: 2)[NMOS, $20 upright(k) ohm$ load], [Measured], [0.19], [#calc.round((1.5 - 0.19), digits: 3)], [1.5], [2.2], [#calc.round((4.78 - 2.2), digits: 3)], [4.78],
      [Calculated], [#vol(20000)], [#nml(20000)], [#vil(20000)], [#vih(20000)], [#nmh(20000)], [#voh(20000)],
            ),
    caption: [
      Inverter parameters for the resistor-load inverters
      ]
    )
]

||

upbeat pine
#

Why did you add kind: table? Reflex?

wary knot
#

oh i just didnt remove it from their code

#

:p

upbeat pine
#

But that one used tablex, so it was understandable.

wary knot
#

i just copy pasted their code and replaced tablex stuff with not tablex stuff

#

so missed that one

#

:p

upbeat pine
wary knot
#

@robust viper Q: should we measure all unbreakable auto rows with infinite height, even outside of the unbreakable auto row simulation (where using infinite height is needed to fix the overflow bug you had shown)?

#

as in, do you think theres a situation where we wouldnt want that?

bold bluff
#

☠️ Infinite height is needed to fix the overflow bug ☠️

wary knot
#

well

#

it's cuz it's a bit funny

#

the idea is to measure things in the auto row to see how large they will be right

#

well

#

if you set the height at e.g. 50pt for some text

#

and it's unbreakable, meaning it cant spill over to the next page

#

then the text's height will be exactly 50pt

#

i think i'll just do it

bold bluff
#

What would the consequences be?

wary knot
#

it'd be the same as what tablex does

#

aka measure(...)

#

so i'd say the consequences probably wouldnt be very significant

wary knot
#

but idk if i would call that a "problem"

#

cuz here we explicitly made that group of rows unbreakable

#

it'd behave in the same fashion if it were a fixed size row instead of an auto one so

wary knot
#

hey folks

#

look at what will be possible soon enough in your local typstguy

#

" omg column headers are real?!?!?" well it's actually "manual" (you have to place the repetitions, you cant just tell the table 'repeat it for me') but otherwise yes!!! woo

#

full code:

#
#show table.cell.where(x: 0): strong
#show table.cell.where(y: 0): strong
#set page(height: 13em)
#let lets-repeat(thing, n) = ((thing + colbreak(),) * (calc.max(0, n - 1)) + (thing,)).join()
#table(
  columns: 4,
  fill: (x, y) => if x == 0 or y == 0 { gray },
  [], [Test 1], [Test 2], [Test 3],
  table.cell(rowspan: 15, align: horizon, lets-repeat((rotate(-90deg, reflow: true)[*All Tests*]), 3)),
  ..([123], [456], [789]) * 15
)
wary knot
# robust viper somewhat minimized: ``` #set page(width: 10cm, height: 2.5cm, margin: 0.5cm) #se...

aight so,

#set page(width: 10cm, height: 2.5cm, margin: 0.5cm)
#set text(size: 11pt)
#table(
  columns: (1fr, 1fr, 1fr),
  [A],
  [B],
  [C],
  [D],
  table.cell(rowspan: 2, inset: (top: 0pt), lorem(4)),
  [E],
  [F],
  [G],
)

renders "correctly" cuz you get 0pt top + 5pt bottom from above, it's definitely the frame of inset from the previous region somehow getting moved to the next region, i didnt know that was even possible

#

i'd call it a flow layout bug so , maybe not much rowspans can do here, the rowspan code seems to be all correct in this sense

wary knot
#

Cc @robust viper - I've cleaned up a lot of stuff, but the core didnt change (at most got moved into separate functions in rowspans.rs as you requested)

you can take a quick look at things if you want , in particular you might benefit from taking another look at run_rowspan_simulation (the for loop of the algorithm), I made it perhaps more readable (gave better names to variables, used clearer syntax, added more comments and so on) without changing what it does

#

perhaps the most relevant change since your reviews (besides bug fixes) was that unbreakable auto rows now measure with infinite height
in principle this allows them to expand towards the margin, but i think we should consider this to be "working as intended"

#

the "weird spacing" bug you mentioned doesnt seem to be my fault (at least at first), seems to be some bug in the layout engine which is moving an empty inset frame from the previous page to the next (when that frame should just be deleted entirely)

robust viper
wary knot
#

I also debugged it for a bit and I was sure the problem was somewhere in there , but couldn't pinpoint the exact thing haha

#

So that's epic

#

Also btw

#

I think you made a typo in the release workflow fix PR

#

Or something
Cuz it seems to be interpreting vendor-openssl as a separate parameter instead of another --feature

#

Likely an easy fix though

#

I'll test a fix and report back soon tm

vernal herald
wary knot
#

No it means ℒ️

robust viper
#

I had tested it in my fork previously but not after changing the feature flag ...

wary knot
#

oh wow

#

i updated the rowspans branch to main and not only did the test case for the inset bug change (the table you sent), but also another test case

#

turns out the inset bug was already in one of my tests, i just didnt realize

#

lol

#

before vs after

#

well it's very zoomed in, thanks discord

#

lol

#

github actions is down

#

awesome

#

pull requests are also broken

#

my commits dont show up

robust viper
#

I wondered whether you had noticed that

robust viper
wary knot
#

when i first made it the "a a" part was overflowing from the small segment at the top

#

and then i rebased the PR later and it just got magically fixed

#

lol

#

and then perhaps I noticed that it was a bit off but it felt like an improvement from before so maybe i just didnt care enough lol

wary knot
wary knot
#

actions are still a bit weird

#

but they run at least

wary knot
#

okay well

#

RIP reviews then apparently

#

πŸ˜‚

#

but it's ok

#

I believe I've addressed all of them

#

also btw i had a bit of a realization regarding headers

#

maybe that "problem" I was talking about isnt a real problem

#

maybe it's ok to reduce 100% depending on the available size

#

i'm gonna have to experiment with this :p

bold bluff
wary knot
#

or, in other words, the total height available

bold bluff
wary knot
#

header syntax just dropped

#

while testing before, i was using tablex-like header-rows: 2, cuz it's much easier to program that

#

lol

#

but now we have a proper thingℒ️

#

headers are actually doing better than i anticipated

#

main functionality is basically done (for the MVP)

#

here MVP = a single header, repeating from top to bottom

#

in the future we'd add footers, and headers starting from arbitrary points in the table / footers ending at arbitrary points in the table

#

and even further into the future (as was suggested to me) we'd add header / footer levels (repeat parts of headers, change those parts over time)

#

cant wait to work on those

#

though theres one small problem

#

uni starts in 01 days

#

big oofer regarding available time so yeah

bold bluff
#

This is probably more than any one could hope for in a single release.

wary knot
#

but now i have made some major strides

#

I might have further news to share soonℒ️ ... πŸ‘€

robust viper
upbeat pine
wary knot
#

@robust viper please make sure to not check the PR diff as it is scary (it includes all of the rowspan diff)

the real diff is much smaller and simpler πŸ™‚

#

I had a blast with this haha
But it's finally ready πŸ˜„

robust viper
#

@wary knot rowspans can merged, right?

wary knot
#

yep

#

i added one extra commit today (backported from headers)

#

just a small little adjustment, nothing too important

robust viper
#

queued for merge

wary knot
#

woo-hoo πŸš€

#

with headers, we'll have basically completed the MVP πŸ˜„

#

tablex 2 is real πŸš€

vernal herald
wary knot
#

my hands are actually shaking right now, this was such an epic journey

#

haha

vernal herald
#

I can't believe this.. you magnificent beast.

vernal herald
sly sparrow
#

lets go

signal breach
#

Thanks a lot, @wary knot You've put a lot of work and great care on this!

#

Better table support is probably the only thing in Typst that I really missed from LaTeX

wary knot
#

Indeed I think this will be very important for Typst's adoption

#

At least for the use cases where tables shine

#

But you will also be able to use the new table features for layout purposes as well, the sky is the limit here

#

Haha

#

Anyway but yeah, I'm glad we got this far, and all of you were very important and valuable, even those who aren't in this thread

#

So thank you all as well 🀝🀝

#

The feedback was fundamental, that's for sure

midnight heron
#

Live from the table trenches

vernal herald
#

This is actually waaaay more impactful, beautiful and awesome than it looks like to begin with........

midnight heron
#

I'm working on touching up the docs examples for tables a bit.

#

Thinking that tables should have a guide. Users often want to create some. Adding a comprehensive section to the tutorial could be overwhelming while a terse reference does not have all the examples a user could want. An example of where the reference is not very useful right now is that it sends you off to the grid page for track definitions... So I think that a table guide / tutorial with more non-trivial examples is a good idea

#

@wary knot What do you think about these alternative examples for table (below the top example) ...

#

... and table.cell? They show the same principles but have been touched up a bit design-wise.

#

Will send the source if you want to integrate those

sly sparrow
#

@midnight heron May I recommend adding the fact that you can import all the table.x stuff so that you don't always have to add the table in front if you use something often. At least I think that was possible, ignore if I am wrong about that lol

midnight heron
#

I'll also post some thoughts about the docs here as I go

  • The grid docs and table docs should mutually explain when to use one and the other (namely, grid is for layout and table carries semantic meaning)
  • Both functions take an (x,y) => thing closure for multiple arguments. I think the docs should explain that pattern explicitly somewhere
  • The "Styling the grid" section explains how grid.cell is an override which is good, it should also mention how hline and vline override strokes
  • The same section contains an enumeration of the different levers to customize a grid in prose, maybe that should be a Markdown list
  • Many grid examples should arguabely be tables. The grid reference should focus on the grid as a primitive to achieve layouts such as subfigures, complex callouts, and more
  • The spread example is a bit lost between two styling-focussed examples
midnight heron
sly sparrow
#

Yea just a small mention will do cause without it people won't assume it's possible and might complain about verbosity

midnight heron
#
  • The x.cell arguments that just say the x override should make x link to the overridden argument
  • hline and vline need examples that demonstrate the behavior of x/y and position. The large paragraph there may otherwise be overwhelming
  • If you demonstrate one thing for tables in an example, you can link from grid and say something like "You can find an example for use of the position argument in the table docs" and vice-versa.
plucky haven
midnight heron
#
  • An example for colspan or rowspan is missing
#

Both functions take an (x,y) => thing closure for multiple arguments. I think the docs should explain that pattern explicitly somewhere
Just noticed that you did that in the grid docs. Maybe something for the guide then.

midnight heron
grand haven
upbeat pine
#

For inset there probably should be (separate) an example that demonstrates its "real" use. But I don't really use it, so...

plucky haven
#

I think we should exhibit beautiful examples, since we will not use inset: 0pt if this is not perfect.

upbeat pine
#

Yes, examples should only make people go "wow" and "cool". So inset (and other fields) should be used to make things pretty and not the other way around.

plucky haven
#

btw the trip process is funny and cool πŸ˜„.

robust viper
#

I agree that examples should look good, but not every example needs to be "wow" because an example should also be focussed and not tweak every single property to its optimum point.

#

Rather, it should explain how to use one specific property, or a set of specific properties in combination in a terse way.

upbeat pine
#

not tweak every single property to its optimum point.
Yes.

#

Another interpretation is to give "real world" examples of how to use specific stuff aaand it should look nice (with minimal tweaks) and show how user can improve their tables/documents.

lone nova
#

I think that this example also must be included to documentation

midnight heron
#

Although the question is whether the guide should have a progression like the tutorial or answer a bunch of questions like the page setup guide

upbeat pine
#

In my experience, guides and tutorials are mostly the same thing and can overlap significantly if not fully.

#

If the guide will have a progression, then in order to accommodate several examples, it will obviously need to have several progressions. Perhaps a healthy mix of "step-by-step guide" and "to get A do B reference" would be ok.

turbid granite
# midnight heron I'll also post some thoughts about the docs here as I go - The grid docs and tab...

For the second bullet point, it could be nice to have a more explicit function type to be able to display in the docs. i.e. notation (x: Int, y: Int) -> Content could be listed as one of the valid types, then it would give a specific place to discuss how that is valid. I know this is more tied up in the type rework, but would be nice to see this pattern. (and would be really useful for people familiar with functional programming to see this option explicitly)

#

And could use => as the arrow to be fancy and have the type match the code

wary knot
wary knot
# midnight heron I'll also post some thoughts about the docs here as I go - The grid docs and tab...
  • agreed

  • agreed, could perhaps be in one of the fields

  • agreed, though I think line and stroke customization might demand its own subsection, e.g. explain how the stroke override order is grid stroke field -> cell stroke field -> hline/vline, and also mention the repetition of top and bottom border stroke which will have higher priority than conflicting cell strokes (also a repeated header's bottom stroke functions in a similar way, and so will footers in the future)

  • perhaps; if it looks good then im ok with it

  • sure, I can get behind that, though I failed to find a really good example for it. I had thought of making a chess board kind of thing, but the chess emoji didnt look very good / overall I wasn't very satisfied with it, perhaps you can make the idea work though (I sent the source here #1182377294682128445 message )

#

overall I think we're in a good direction here for table docs, feel free to adjust what you think is needed, after all I had to focus on making tables look goodℒ️ rather than writing the best docs haha

wary knot
#

cuz it's rare you will actually need to use a lot of the customization primitives

#

at least outside of a template

#

but when you have like a ton of table.cell calls then yeah i think it might make more sense

wary knot
upbeat pine
#

PSA: using numbered list is better when you're discussing something, because the other party can easily reference any of the points by number which it always easier.

wary knot
upbeat pine
#

that's what I'm talking about. if one starts using "unordered list", then responders kinda have to use it as well. And it doesn't look very readable (like here) (at least to me).

robust viper
upbeat pine
#

Unless you need to reference the same point multiple times. Then you need to paraphrase and things like that.

upbeat pine
#

I also don't get if all of these are gonna go to the next release or just the repeatable header and that's it.

sly sparrow
#

if you read the PR, it specifies a repeat parameter which is true by default

#

and no, the intent isn't to have everything in that list ready for the next release. its just listed already, though Pt. 5a might still make it

wary knot
upbeat pine
#

Yeah, I skipped right through it, thanks.

upbeat pine
sly sparrow
#

realistically, i doubt @wary knot is sweating to get more out the door so close to release

upbeat pine
#

I just think that @robust viper is waiting for @wary knot and vise versa for that last PR to be merged and then the "testing" phase will start. Unless the docs aren't finished yet.

#

From the #1176122103355953162 it feels like we either will have templates in the next release, or the work there is just being done "in parallel" and will not be included in the next release, as it is probably still WIP (from the looks of it).

wary knot
#

It is close to being merged i believe, im gonna make another commit today in response to a review and I think that'll be it, or almost it

robust viper
upbeat pine
#

I can't believe how huge this update will be. The margin between what I thought it would be and what it will actually be is unbelievable.

wary knot
#

later today i'll give my OK

random mica
#

and don't forget context!

wary knot
#

ok so

#

I enabled my brain debugger (usually occurs involuntarily when I take a shower)

#

and i may have found 2 logic bugs that way

#

(not tested yet)

#

:p

#

they are small and easy to fix but it's kinda funny to think about

wary knot
#

well i managed to reproduce one of them at least lol

bold bluff
wary knot
#

i wasnt being literal

#

by "brain debugger" i meant that i was "debugging my thoughts" by thinking further about them

bold bluff
#

What exactly is happening in the example?

wary knot
#

basically this:

#
#set page(height: 10em)
#table(
  columns: 2,
  table.header(
    [a], [b],
    [c],
  ),
  table.cell(x: 1, y: 1, rowspan: 2, lorem(80))
)
#

i included a cell in the header after the header was created

#

so the header didnt expand to include all of the cell's spanned rows

#

so the rowspan got repeated but the row under it didn't and it generated a huge mess

bold bluff
#

So can you fix it by adding the header later in the process. Or are you out of luck?

wary knot
#

i fixed it already

bold bluff
#

So speedy.

wary knot
#

in the fixup phase when replacing unspecified cells with empty cells, I also added a check

#

if the cell starts inside the header then make sure the header is at least as long as y + rowspan

#

anyway

#

theres still one bug left

#

it's not the one i was thinking of earlier

#

which is interesting

#

lol

bold bluff
#

Well one that we know about.

wary knot
#

yeah

#

but i mean i dont want to stall the update with infinite tests

#

so i'll just work on fixing this one mostly

#

cuz it's rather important

#

like. theres something very fishy here

#

the rest of the table just vanished

#

lol

bold bluff
#

Hmm πŸ€” had to go somewhere.

#

What happens when you break nested tables with headers across pages?

wary knot
#

it's fine

#

you get two headers :p

bold bluff
#

That’s a really satisfying result.

#

What if the nested table is the header. (I have not figured out what it should look like)

wary knot
#

well , the header cant break across pages

bold bluff
#

Maybe infinite recuse

wary knot
#

so it would just lead into a static nested table

bold bluff
#

Smart rule.

wary knot
bold bluff
#

So you have xy coordinate placement. What happens when you place elements not at 0,0 say place elements at 5,5 and 7,7. I should probably just build main to see if I can find anything.

wary knot
#

you mean #place?

bold bluff
#

Table.cell

wary knot
#

you mean if you write table.header(table.cell(x: 5, y: 5)[something])?

bold bluff
#

Ohh that sounds dangerous

wary knot
#

and (x: 7, y: 7) alongside it

#

well that particular case will just error

#

cuz then it wont start at the first row

#

the header has to start at y = 0

#

currently

#

but if u place something at the first row so it starts there then it should do exactly as you asked (the header goes all the way from rows 0 to 7 , and has some content at those two positions, the rest of cells are empty)

bold bluff
#

Nice. Well I couldn’t think of anything. Seems like a pretty nice system. What features are most likely to have anomalous behavior?

wary knot
#

well, mostly arbitrary positioning of cells and rowspans

#

i fixed a bug with the former, now fixing a bug with the latter

bold bluff
#

Can a rowspan and a repeat header align in a bad way?

wary knot
#

it starts at the top and is cut

#

and then later resumed

#

but as if it still had stuff going on in the empty pages

#

hm wait

#

lol

#

nevermind what i said

#

thats not a rowspan

#

the rowspan is in the header

#

lol

#

anyway something it did screwed something up

bold bluff
#

I agree that the table is not perfect. But it does have character.

wary knot
#

yeah

#

it's pretty cool already

#

im mostly hitting into edge cases here

#

lol

#

okay

#

i see

#

it's a problem in the header orphan check

#

basically it checks if a page would only have a header

#

if so, all rows are removed

#

but in this case it failed to check that properly

#

cuz a header row is empty and was removed

#

and it just checks row amount so yeah

#

should be easy fix

bold bluff
#

Nice πŸ‘.

#

Can the header be made of rowspans of different lengths?

wary knot
#

yes

#

the longest one wins

bold bluff
#

What gets filled ?

wary knot
#

i mean, all the cells get respectively filled

#

no overlap in principle

#

ok the table is back

#

we are almost there

#

just gotta deal with this guy

#

that's not where they should be

bold bluff
#

will have to evict.

#

Ok what happens when you have knitted rows spans. And a header row. . Picks the longest. ( I forgot if this would be breakable ) a the the longest rowspan gets picked for repeat headers. And if I have all my cell colored blue then I’d have some un filled gaps after that first repeat header.

wary knot
#

yaaaay

wary knot
#

turns out my fix worked but

#

it was a bit of a hotfix

#

lol

#

cuz i actually found out the real problem

#

the rowspan in the header thought it started from the first page, but it clearly doesnt

#

so whatever i fixed wasnt supposed to be even run

#

very cool

#

now we have a proper solution

vernal herald
#

Puah the fix, and then go take another shower πŸ€”

wary knot
#

the ultimate secret

vernal herald
# vernal herald

This was a reaction to your tables are real sentence. My father in law laser cut these custom coasters for me. Obviously, I'm a white beard kind of guy.

wary knot
#

the hardest commits to make are always the shortest ones

#

πŸ˜‚

wary knot
wary knot
#

ok i fixed two more things

#

in principle stuff should be ok now

wary knot
vernal herald
#

Okay. Talk to me like I'm 5.. Isn't this like all? Also the stuff with tables that go beyond one page?

wary knot
#

it's all for this release, yeah

#

but there are a few more ideas in the tables tracking issue

vernal herald
#

Uhm.. Which ones? I'll take a look.

#

I don't understand any of these ideas. But cool!

wary knot
#

well

#

at least repeatable footers are easy to understand

#

it's like a header but on the bottom

#

regarding arbitrary positions, right now headers must be in the first row (and initially footers would have to end at the last row); the idea is to be able to place headers anywhere, such that they wouldnt be a header until they show up for the first time

vernal herald
#

But if I'm making simple ass tables, with headers being.... First row, then you've actually done that? Tables gave 90% feature set done?

wary knot
#

the most common use cases were covered, if that's what you mean, yeah

wary knot
#

once they're laid out once they stay

#

subheaders would take that to the limit: you'd be able to have multiple headers active at the same time

#

so you'd get a group of rows and it'd stay, then another group of rows and it'd stay below the previous one (cuz it has a higher "depth" / "level"), then you'd get another group of rows with the same level as the first so it'd replace the first one, and so on

vernal herald
#

While it sounds cool.. Can I ask, who asked for this? It sounds like Excel at this point.

wary knot
#

itΕ› mostly useful when you have something like
| Table |
| A | B | C | D |

and you just wanna change the "A B C D" part

vernal herald
#

Oh yeah.

#

That's a flattable in R.

#

Or ftable. Those are handy if you make a pivot / contingency table with multiple dimensions..... πŸ’€

#

Alright, yeah those would be cool.

wary knot
#

yea there are a few use cases

#

but let's not go too far for now

#

haha

#

i'll probably be making footers before that

#

at least, would be a greater gain in value

vernal herald
#

Damn, I went from "what is this" to "when can I have this" pretty fast πŸ€£πŸ˜†πŸ€¦β€β™‚οΈ

wary knot
#

what im thinking of would have a more vertical structure

#

but yeah it's similar (changing only part of a header)

vernal herald
#

Awesome work. This isn't in Word I think...

lone nova
#

One important thought: Can we make separate title / caption for a table?

Not that one that included in the figures!

First of all, figures can not properly show a miltipage table. Secondly, table may have different captions on different pages (like"Table 1. Beginning", "Table 1. Continuation", "Table. End")

wary knot
#

we'd need to have some design discussion first however

wary knot
#

btw @midnight heron it might be interesting to add an example like this too (but with repeated headers as well now probably)

#

source for that one is

#show table.cell.where(x: 0): strong
#show table.cell.where(y: 0): strong
#set page(height: 13em)
#let lets-repeat(thing, n) = ((thing + colbreak(),) * (calc.max(0, n - 1)) + (thing,)).join()
#table(
  columns: 4,
  fill: (x, y) => if x == 0 or y == 0 { gray },
  [], [Test 1], [Test 2], [Test 3],
  table.cell(rowspan: 15, align: horizon, lets-repeat((rotate(-90deg, reflow: true)[*All Tests*]), 3)),
  ..([123], [456], [789]) * 15
)
midnight heron
#

Very nice!

#

I like it when the examples contain some non-blind text. Makes it nicer to look at and the user's need to abstract less between the example and their use-case / emphasize more

#

With this example, you could put something like "USD per day" and then put "Blue chip", "Fresh IPO", "Penny stock" in the column headers

wary knot
#

Feel free to adjust it as needed for the docs as I'm running kinda low on creativity right now haha

midnight heron
#

Hey! I was wondering whether you have a docs PR in the works or whether we should edit them ourselves. Either is fine, but we need to know since it's one of the last things to do before .11 can enter testing.

wary knot
#

i might be able to help over the weekend though

#

if you can get something going for now that'd be great, and I can then give feedback and/or add further suggestions later

wicked totem
#

@midnight heron - let me know if I can help in any way - examples and such?

midnight heron
#

It would be great if you could add some!

wary knot
#

the rules there are a bit intricate so maybe I should try to clarify them

#

in general grid stroke < cell stroke < hline / vline in terms of priority
but there are some edge cases and exceptions
e.g. top / bottom border, header line, conflict between two things of same priority (=> cell below / to the right wins, hline / vline specified later wins)

#

so it's probably best to have that at least documented somewhere

#

the rest should be relatively simple to write docs for (colspan / rowspan, headers)

wary knot
#

Btw @midnight heron i had an idea for a grid layout example

#

i think we could demonstrate the usage of colspans and rowspans to make a fully customized page layout with grid

#

For example, you'd use a rowspan to make a "sidebar" of sorts, and a colspan to make some header stuff

#

Though i think repeatable headers are mostly useful for tables , at least while they're in MVP stage (only at the first row for now)

wary knot
midnight heron
wary knot
#

to clarify a bit for now though, i think my idea would have some applicability in CVs for example , where you often need to pack a lot of information into one page, so having colspans and rowspans gives you lots of flexibility in that regard , i believe

#

Which is something you'd otherwise have to use nested grids for

night python
#

i was also thinking in a similar direction, namely slide layouts. when a slide spans multiple pages (like a bibliography) repeatable grid headers (and footers) would work well for slide headers/footers

wary knot
#
#set page(height: 16em)
#grid(
  columns: 2,
  inset: 5pt,
  stroke: (x, y) => if x == 0 and y > 0 { (right: (paint: luma(180), thickness: 1.5pt, dash: "dotted")) },
  grid.header(
    grid.cell(colspan: 2)[*Previous Experience* #box(width: 1fr, line(length: 100%, stroke: luma(180)))]
  ),
  grid.cell(rowspan: 2)[*2012*],
  [*Pear Seed & Co.* - Lead Engineer _(Mar - Jun)_
  - Did a great job
  - Helped the company grow],
  [*Mega Corp.* - VP of Sales _(Jul - Dec)_
  - Raised a lot of funds],
  [*2013*], [*Tiny Co.* - CEO _(Jan - Dec)_
  - Had lots of clients],
  [*2014*], [*Medium Co.* - CTO _(Jan - Mar)_
  - Did a lot of work]
)
#

though my initial idea was a bit more general than that

#

like using grid to separate the different parts of a page (header (colspan), sidebar / aside (rowspans), body (one or more regular cells), footer (colspan))

wary knot
#

with 15em page height and [*2013* #colbreak() *2013*]

wary knot
#

hmm

wary knot
#

now that's closer to what I was thinking of (although I also like the previous example)

#

a page done entirely using grid

#

here's the code

#
#set page(height: auto, margin: 0pt)
#let title(it) = strong(smallcaps(text(32pt, it)))
#let subtitle(it) = emph(text(20pt, it))
#let titlebar(it, ..block-args) = text(white, block(width: 100%, fill: blue, inset: 10pt, ..block-args, align(center, it)))
#let titled-cell(title, body, ..block-args) = stack(dir: ttb, titlebar(title, ..block-args), block(inset: (top: 5pt), body))
#grid(
    columns: (1fr, 1fr, 1fr),
    inset: 10pt,
    grid.header(
        grid.cell(
            colspan: 3,
            titlebar(fill: green.darken(50%))[#title[Our Epic Project]\ #subtitle[A study on plants]]
        )
    ),
    grid.cell(
        colspan: 2,
        titled-cell[*Our Origin*][We come from a small town in our country.],
    ),
    titled-cell[*Our Team*][We are Bob, John, Kelly and Nate.],
    grid.cell(rowspan: 2, titled-cell[*Our Story*][#lorem(50)]),
    titled-cell[*Our Goals*][
    - We seek a better future.
    - We seek to learn more.
    - We seek to push the boundaries of knowledge.
    ],
    titled-cell[*Our Values*][We are always very positive.],
    grid.cell(colspan: 2, titled-cell[*Contact Information*][
        - You can find us on social media; our handle is ThePlantsGroup.
        - You can shoot us an email: #underline(text(blue)[email\@doesnt.exist])]),

    grid.cell(colspan: 3, titlebar(fill: orange.darken(20%), title[Join us now!]))
)
#

i feel like the code is pretty simple even

#

compared to what it could need to be today

sly sparrow
#

with html export and this, typst will truly be reinventing table-based layouts on the web

wary knot
#

so anyway folks

#

i was a bit bored and you know

#

it was a bit tempting so uh

#

uh

#

drops and runs away

vernal herald
#

Ah the footer thing

wary knot
#

πŸ˜‚

#

I swear I had the best intentions @robust viper ❀️
πŸ˜‚

#

though i like how i barely had to change anything in rowspans.rs

#

nobody likes debugging rowspans for +inf hours

#

if headers were simpler than rowspans, footers are even simpler than headers haha

#

Man this was so insane i actually wrote "rowspan tests" instead of "footer tests" in one of the commits lol

#

Im gonna catch some sleep ASAP πŸ˜‚

lone nova
#

It seems to me that in typst 0.11 we will have the entire set of capabilities for the table, because:

  1. The typst team is trying to write documentation for the available features

  2. @wary knot manages to prepare a new PR during this time

  3. See item 1. 🀣

wary knot
#

Nah i will stop for real now lol

#

I could only do this cuz footers use the same infrastructure for headers

#

So it was very fast

#

I literally did it in the past few hours

#

But anything more complicated than that would have been impossible to do that way :p

vernal herald
#

The footer thing makes the use case with polylux fully possible now πŸ‘

midnight heron
vernal herald
#

First part of the sixth task in #3001.

#

Those docs better be outstanding, because it is a bit tricky to parse.. So the repeatable footer can be a collection of rows.

wary knot
#

The examples have two rows as header and footer

vernal herald
#

Yeah.. Actually it makes perfect sense. Sorry..

wary knot
#

It's ok

bold bluff
wary knot
#

ok

#

ive done several review passes, things are looking good in general

#

later today i'll probably add some extra tests just in case and that'll be it

#

πŸ™‚

robust viper
#

@wary knot since footers are so similar to headers, cloning the full header test suite (including yet again 400KB of images) feels excessive to me. can't we validate that it behaves mostly the same with a smaller, more focused test suite? while generally, having more tests is a good thing, it can also become a maintenance burden.

wary knot
#

Just don't know the best way to do it ATM

#

But I think many of those tests end up testing the same thing for headers and footers so it should be ok to just use the tests for both purposes

robust viper
#

I've pushed to the PR

#

I will merge now

wary knot
#

oh okay

#

yea i was asleep sorry πŸ˜‚

robust viper
#

no worries

wary knot
#

either way we can always simplify things further later if needed so should be ok

robust viper
#

I want to get rid of the reference images anyway

#

I just want SHA of the image bytes really

#

For the tests themselves it isn't a problem, just the dev & review experience will need some solution

wary knot
#

i think you did a good job in shortening the tests actually

#

i was a bit worried about how you removed the gutter test

#

but another test had it so

#

either way yeah we can improve things further in the future

robust viper
#

I tried my best to retain the intent, though it was a bit hard for some of the tests

wary knot
#

yea i think it's fine

wary knot
#

okay

#

we can now announce

#

We can all agree that this update is gonna be Very Epicℒ️

sly sparrow
#

Biiig changelog

vernal herald
#

It is a great day for tables.

#

The footer thing is nuts.

wary knot
#

yeee

#

it was definitely fun to work on them haha

grand haven
midnight heron
#

Do we have a simple (<15 short lines) example in mind for table footers?

#

Otherwise typst/typst#3593 is ready to merge

wary knot
vernal herald
wary knot
#

i guess the example does work for non-repeated, purely semantical footers though

#

but that's indeed a concern

midnight heron
#

You ought to see something with an example, which is hard because show set rules for headers and footers do not yet work

wary knot
#

yeah we'd need to adapt the styling system a bit for that to work i think

vernal herald
#

Ah, just write a Source note. These numbers are accreditied to some institution or something.

upbeat pine
wary knot
#

read the test title

#

it's basically what it's testing

#

the header shouldnt be an orphan in the page

#

so it is sent to the next page

upbeat pine
#

oh, I see.

wary knot
#

should we use the #colbreak() trick for this example btw?

#

cc @midnight heron

midnight heron
#

Ah, the one where it douplicates the label?

#

I removed it because its a bit hacky and assumes knowlege of how many pages the table breaks on to

wary knot
#

hmm

#

yea that's true, though I just wanted to let people know it's possible

#

might be a common question

#

but in the future we'll certainly have a proper way to do this

#

i think we can leave it as is for now, and then in the next update we add a proper "repeatable rowspan" mechanism πŸ‘

#

oh hey

#

the chess example was added

#

huzzah

bold bluff
#

Can you do a continued on next page example. It would make me feel happy πŸ˜ƒ.

wary knot
#

well, there's no native support for that yet

#

but you might be able to hack something with footers

wary knot
#
#set page(height: 8em)
#let page-counter = counter("table-counter")
#table(
  columns: 3,
  ..([a], [b], [c],) * 5,
  table.footer(
    table.cell(
      colspan: 3,
      stroke: none,
      context {
        let this-page = here().page()
        if this-page != page-counter.final().first() {
          [Next page!]
        }
        page-counter.step()
      }
    )
  )
)
upbeat pine
#

But this would only work if the table ends at the last page of the document. Or I'm dumb...

#

Ok, this is actually genius. I didn't read the page-counter.step() line.

#

Wait, no, there is a different flaw. It only works if the table starts at the beginning of the document.

#

If it were to start at the page 10, then the counter would go from 0 to 5 and the page would go from 10 to 12. So the condition will always be true.

wary knot
#

ah right

#

hold on lol

#

ok

#
#set page(height: 8em)
#let page-counter = counter("table-counter")
#show table: it => {
  context {
    let this-page = here().page()
    page-counter.update(this-page - 1)
  }
  it
}
#table(
  columns: 3,
  ..([a], [b], [c],) * 5,
  table.footer(
    table.cell(
      colspan: 3,
      stroke: none,
      context {
        let this-page = here().page()
        if this-page != page-counter.final().first() {
          [Next page!]
        }
        page-counter.step()
      }
    )
  )
)
#

there we go

#

wont work very well with nested tables

#

actually i can fix that

upbeat pine
#

You need to compare to page-conter.get().

wary knot
#

hmm

#

yeah i think you're right

#

it will still have problems

#

with multiple tables

#

we need a way to generate unique keys for counters

#

lol

upbeat pine
#
#set page(height: 8em)

first page
#pagebreak()

#let page-counter = counter("table-counter")
#table(
  columns: 3,
  ..([a], [b], [c],) * 6,
  table.footer(
    table.cell(
      colspan: 3,
      stroke: none,
      {
        page-counter.step()
        context {
          if page-counter.get() != page-counter.final() {
            [Next page!]
          }
        }
      },
    ),
  ),
)

#pagebreak()
last page
upbeat pine
# wary knot with multiple tables

The only obvious problem is that this counter is single-use only. You should use a new one or reset this one for each new table use.

wary knot
#

okay

#

i have a solution

#
#set page(height: 8em)
#let page-counter-counter = counter("yak-is-being-shaved")
#show table: it => context {
  page-counter-counter.step()
  let page-turn-counter = counter("table-page-turn-counter-" + str(page-counter-counter.get().first()))
  page-turn-counter.update(here().page() - 1)
  show <on-page-turn>: it => context {
    if here().page() != page-turn-counter.final().first() {
     it
    }
    page-turn-counter.step()
  }
  it
}
#table(
  columns: 3,
  ..([a], [b], [c],) * 5,
  table.footer(
    table.cell(
      colspan: 3,
      stroke: none,
      [#[Next page!] <on-page-turn>]
    )
  )
)
#

adding more tables works

upbeat pine
#

I would like this hack to not require a global-scope custom counter, because it will feel and look more of a hack than a proper solution. Does the same-name counter persist if is called from different local scopes?

wary knot
#

yeah it persists

#

of note, tablex uses something like this

#

lol

#

a counter for counters

upbeat pine
wary knot
#

the second global counter there is unnecessary

#

so i removed it

#

ok ive moved the counter to inside the show rule too

#

personally i prefer keeping it global

#

but that will work

#
#set page(height: 8em)
#show table: it => context {
  let page-counter-counter = counter("yak-is-being-shaved")
  page-counter-counter.step()

  let page-turn-counter = counter("table-page-turn-counter-" + str(page-counter-counter.get().first()))
  page-turn-counter.update(here().page() - 1)
  show <on-page-turn>: it => context {
    // Only display this content if we're not at the last page of the table
    if here().page() != page-turn-counter.final().first() {
     it
    }
    page-turn-counter.step()
  }

  it
}

#table(
  columns: 3,
  ..([a], [b], [c],) * 5,
  table.footer(
    table.cell(
      colspan: 3,
      stroke: none,
      [#[Next page!] <on-page-turn>]
    )
  )
)
#

final solution

#

also darn context is so ergonomic to use lol

upbeat pine
# upbeat pine You need to compare to `page-conter.get()`.

Bro, What I said about is literally more sane even with double counters:

#set page(height: 8em)

#show table: it => context {
  let table-counter = counter("table")
  table-counter.step()
  let table-part-counter = counter("table-part" + str(table-counter.get().first()))
  show <table-footer>: footer => {
    table-part-counter.step()
    context {
      if table-part-counter.get() != table-part-counter.final() {
        footer
      }
    }
  }
  it
}

first page
#pagebreak()

#table(
  columns: 3,
  ..([a], [b], [c],) * 6,
  table.footer(
    table.cell(
      colspan: 3,
      stroke: none,
      [#[Next page!] <table-footer>]
    )
  )
)

#pagebreak()
last page
upbeat pine
wary knot
#

lol sorry

#

but yeah you're right, that way works cuz the footer only appears once for each page so yeah

#

huzzah

upbeat pine
#

The only thing left to do is to remove the label so that you can define the footer normally. This would be as clean as a normal code, but with one smart show rule.

wary knot
#

yeah unfortunately show rules on table.footer wont work

#

i wanna make it work but it'd need some coordination with the rest of styling system

#

in particular there's no show-show so that's even harder

#

show-set is something that is much easier to make work

upbeat pine
#

But I tried the hackable hack and of course you can't mutate read-only variable and children on elements and of course you would hit show rule recursion if you are to recreate the table from its fields/children.

wary knot
#

yeah i think a better approach would be a predefined footer

upbeat pine
#

Oh, right, there is no point in recreating the table, since the table itself isn't split into multiple callbacks. So it wouldn't work either way.

wary knot
#

also theres one shortcoming with this approach: footers have the same height across all pages

upbeat pine
wary knot
#

so there will be some blank space at the end of the table

#

not that bad though, some negative vertical space solves it

wary knot
#

then you'd just write #next-page-footer(5)

upbeat pine
wary knot
upbeat pine
# wary knot something like `#let next-page-footer(columns) = table.footer(colspan: columns, ...

Hmm, yeah. This already looks clean:

#set page(height: 8em)

#let table-footer(repeat: true, ..cell-args) = table.footer(
  table.cell(
    ..cell-args.named(),
    [#cell-args.pos().first() <table-footer>]
  )
)

#show table: it => context {
  let table-counter = counter("table")
  table-counter.step()
  let table-part-counter = counter("table-part" + str(table-counter.get().first()))
  show <table-footer>: footer-content => {
    table-part-counter.step()
    context {
      if table-part-counter.get() != table-part-counter.final() {
        footer-content
      }
    }
  }
  it
}

first page
#pagebreak()

#table(
  columns: 3,
  ..([a], [b], [c],) * 6,
  table-footer(colspan: 3, [Next page!])
)

#pagebreak()
last page

There is a quirk that now footer also gets all the cell arguments, but at least it's even more clean than using footer(cell()).

#

I also marked the label's element as footer-content, because it's not a footer and therefore you can't apply inset set rule.

wary knot
#

ok

#

this chat is being heavily spammed lol

#

but here's a fully integrated solution

#
#set page(height: 8em)

#let next-page-table(next-page-content: [], ..table-args) = context {
  let columns = table-args.named().at("columns", default: 1)
  let column-amount = if type(columns) == int {
    columns
  } else if type(columns) == array {
    columns.len()
  } else {
    1
  }

  // Counter of tables so we can create a unique table-part-counter for each table
  let table-counter = counter("table")
  table-counter.step()

  // Counter for the amount of pages in the table
  // It is increased by one for each footer repetition
  let table-part-counter = counter("table-part" + str(table-counter.get().first()))
  show <table-footer>: footer => {
    table-part-counter.step()
    context {
      if table-part-counter.get() != table-part-counter.final() {
        // Display the footer only if we aren't at the last page
        footer
      }
    }
  }

  table(
    ..table-args,
    table.footer(
      // The 'next page' content spans all columns and has no stroke
      // Must be selectable by the show rule above which hides it at the last page
      table.cell(colspan: column-amount, stroke: none, [#next-page-content <table-footer>])
    )
  )

  // Compensate for the empty footer at the last page of the table
  v(-measure(next-page-content).height)
}

first page
#pagebreak()

#next-page-table(
  next-page-content: [Next page!],
  columns: 3,
  ..([a], [b], [c],) * 6,
)

f

#pagebreak()
last page
#

also compensates for the empty footer space at the last page

upbeat pine
#

I'm pretty sure it wouldn't work when stroke is normal, i.e., not none. So I made a rect as a table.cell (inside table.cell) so that you can do all the regular cell stuff, but if it is the last page, then there will be literally nothing. But it also adds complexity in passing different arguments to different functions.

#set page(height: 8em)

#set table.cell(stroke: green)

#let table-footer(repeat: true, stroke: auto, ..cell-args) = {
  table.footer(
    table.cell(
      ..cell-args.named(),
      stroke: none,
      // table.cell's default inset: 5pt
      context pad(-5pt, rect(
        // probably stroke is not the only field that should be processed
        stroke: if stroke == auto {
          if table.cell.stroke == (:) { auto } else { table.cell.stroke }
        } else { stroke }
      )[#cell-args.pos().first() <table-footer>]),
    )
  )
}

#show table: it => context {
  let table-counter = counter("table")
  table-counter.step()
  let table-part-counter = counter("table-part" + str(table-counter.get().first()))
  show <table-footer>: footer-content => {
    table-part-counter.step()
    context {
      if table-part-counter.get() != table-part-counter.final() {
        footer-content
      }
      else {
        v(-5pt * 2)
      }
    }
  }
  it
}

first page
#pagebreak()

#table(
  columns: 3,
  ..([a], [b], [c],) * 6,
  table-footer(colspan: 3, stroke: red, [Next page!])
)

#pagebreak()
last page

So here global green works as expected, local red also works and none also works. So basically the rect works transparently for the user. At least with stroke. But you definitely can finish the argument parsing, and it probably will "just work".

upbeat pine
#

But at least in the future, everyone can refer to our examples. So it's spam for the good.

wary knot
#

why would it not be?

#

you dont want to add anything around the caption

#

with my example it works just fine with global stroke override for e.g.

upbeat pine
#

Because it also overrides the in-footer cell's stroke.

#

I mean, if the footer must have no stroke, then sure.

wary knot
upbeat pine
#

oh, right

upbeat pine
#

Try also setting the global stroke to green and local (footer) stroke to red.

#

You should have a stoke-width red line at the bottom of the table. Which isn't desired.

wary knot
#

my point was mostly that theres no reason to do that if you want a caption

#

usually you dont add lines around captions

upbeat pine
#

But I didn't have "caption" in mind.

wary knot
#

yea but iiuc "table continues in the next page" is always a caption

#

this is mostly a workaround for it

upbeat pine
#

Will this "caption" work if it is wider than 3 cells?

wary knot
#

yeah

#

you just need to update the colspan attribute

upbeat pine
wary knot
#

yeah you'd remove the real one

#

well, or keep it and have it appear at the last page instead

#

the idea is that in the future figure would support repeatable captions natively

upbeat pine
#

We have a caption for each part. which only differs in one or two words.

wary knot
#

so we're mostly just hacking around it rn

upbeat pine
#

But will they be customizable?

wary knot
#

idk, they werent designed yet

#

we can discuss that in due time

upbeat pine
#

I need the first caption to be the normal and the next captions to have a different prefix/supplement, IIRC. But maybe also remove the separator and body ("Continuation of table 1"). One (or both) of them is definitely correct.

bold bluff
#

Made my day. πŸ˜„

wary knot
#

moment

#

new PR soon (with small fix)

night python
#

i'd argue that the first table example on the table reference page and the example in the readme of Typst's repository should make use of the new stroke customisation features and get rid of this suboptimal full grid of lines.

wary knot
#

theres also something odd here

#

the output doesnt match the code

#

yea it's hidden

#

cc @midnight heron i'd argue that this set rule should be exposed (or even inlined)

#

we definitely need at least one example without vertical lines

pure sundial
#

Maybe make it collapsible? (probably it's planned). Like in mdbook, you can hide parts of code, and show it by clicking on πŸ‘οΈ

pure sundial
#

emph is not applied

midnight heron
robust viper
#

Having fancy-looking tables in the examples requires a fair bit of code every time, which takes away the focus of each example.

vernal herald
#

Suggestion: Link or toggle to see the full example?

random mica
wary knot
#

well, it's not really the point of the feature

#

headers and footers were mostly designed with repetition in mind

#

i'd say that it's better to have the user know that the feature is available, and disable if needed

#

you can also disable globally with #set table.footer(repeat: false)

#

(show rules wont work tho)

random mica
#

we need a query(table::after)

wary knot
#

wdym? fetch what's immediately after the table?

random mica
#

okay fair enough it's a bit weirdly put like that

#

but like element.loc() except for its end

wary knot