-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Table line customization [More Flexible Tables Pt.4] #3393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
For consistency
Table defaults to default stroke, Grid doesn't (no stroke). This is used in FromValue.
On split_vlines, we always go through every row in the current column, so we in particular we go through every vline in this column going through the current row and fold their strokes with the current automatic line's stroke and with the strokes of the cells around the vline. A similar procedure should be done for horizontal lines.
This should make it possible to reuse its code for horizontal lines
Make sure we can properly check for errors, and also that hlines maintain the appropriate order.
No more vector!
Ensure lines with larger thickness are drawn on top, avoiding smaller hlines going "inside" larger vlines.
- table.stroke is now just an override for cell.stroke, it is nothing special. In particular, #table(stroke: red) and #table(stroke: (_, _) => red) are now the same. - therefore stroke never crosses gutters by default now - changed folding priorities: hline/vline now have max priority over cell strokes. They will be used for full control over the table's lines, and they can cross gutters like before. - removed outside stroke for the time being.
Now table stroke overrides per-cell stroke instead of lines, and thus gutter spacing is not crossed.
This is because it is visually swapped when RTL is enabled.
use explicit vlines for that purpose
Actually test the case in which the grid doesn't specify anything for a side, thus folding (unspecified side) with (unspecified side), which should appear as 'none' in the grid.cell show rule
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some remaining questions:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two extra notes on no particular point of the code:
-
Decide about horizontal, vertical APIs (delayed for now).
Don't we effectively have that now thanks to
Sides
? E.g.#table(stroke: (x: red), ..)
. Though I do not understand whystroke: (x: none, y: red)
is different fromstroke: (y: red)
. I think the latter should do the same as the former. -
Side Note: When you use
grid.cell
intable
or vice versa, it just has no effect and proceeds completely silently. I thought that colspan is completely broken for a moment because I used a grid cell in a table. That's a bit of a pitfall. In principle, I like how the lines don't implementShow
, but I guess it's not really an option for cells.
I meant more as a way to specify hlines and vlines (not cell strokes) in bulk, which can be useful when you have gutter. But I delayed that discussion because I'd like to see proper use cases for that first.
It's really just a property of (In other words, the behavior here should be whatever is consistent with the rest of Typst. But I'd also have to somehow override what
Yeah, I myself have faced this problem multiple times during tests 😅 It's not too simple... I think removing Perhaps we could just reject cells of the wrong type for table and grid. Or at least add a warning for it. Not sure. Though I guess a problem with adding an error or warning is that you might be using content from an external source (a parameter or something), and you wouldn't want to inadvertently error deep down based on some decision by the user at the highest level... Eh... I don't know. EDIT: Actually, now that I think about it, even right now the user could pass the appropriate cell element to your function, for example The workaround? When placing content in your table which comes from a parameter, write As such, perhaps adding an error is appropriate, to force library authors to think about this case. |
They are now pushed into vectors of shapes which are prepended in bulk.
Depending on its stroke's source, a line segment may be drawn on top of others.
Per-cell stroke overrides are now assigned a higher priority than global grid stroke.
#table(
columns: (1cm, 1cm, 1cm),
stroke: (y: red),
[a], [b], [c],
[a], [b], [c],
)
#block(stroke: (y: red), inset: 5pt)[hello]
#rect(stroke: (y: red))[hello] shows that something is different with tables for some reason.
Manually applying the default show rule would indeed mess with show rules.
That does sound reasonable (making it error).
I think that would be very rare as most functions wouldn't expect users to pass grid or table cells in the first place. And even then the error would be at least somewhat helpful.
I don't think library authors should have to think about this case or do that. There is just no sane reason for a user to do this and if they do, it's on them. |
We could add
I generally agree, but I think the vast majority of cases where this would be relevant would be in show rules for grid.cell and table.cell - think Either way, as you say, the error would be helpful enough to let you work around it by using the appropriate cell function. Or even just wrapping your cell in a single-element sequence or some other element. |
Fourth task in #3001.
Note: The rowspans PR (Part 3b) was delayed as we are still figuring out the appropriate algorithm for a particular problem, so, in the meantime, we shall have table line customization!
Note: This PR does not include major changes to docs. These were postponed so review doesn't have to take that into account for now. I've added some basic docs for the new elements and options though (just so they don't go missing).
User-facing API
The
stroke
option of grids and tables was changed: now it simply simply changes the stroke around all cells in the way specified. In particular,#table(stroke: red)
is now the same as#table(stroke: (left: red, right: red, top: red, bottom: red))
, which means that the stroke above, below, to the left and to the right of each cell is red.stroke
parameter based on its position (similarly tofill
andalign
):#table(stroke: (x, y) => if y == 0 { (top: blue, rest: red) } else { red })
will have the line above each cell at the first row of the table be blue, exceptionally. For any other rows, the table lines will be entirely red around all cells.You can use
grid.cell(stroke: ...)
andtable.cell(stroke: ...)
to override the grid/table'sstroke
option for a particular cell.There are new elements,
table.hline
(horizontal) andtable.vline
(vertical) - and likewise forgrid
- called explicit table lines, which can be used to manually override table stroke (including stroke set throughgrid.cell(stroke: ...)
).start:
andend:
to control their lengths, andx:
(for vlines) ory:
(for hlines) to specify in which position they appear. By default, they will appear after the last cell you specified, for convenience (a familiar experience forbooktabs
users).{amount of rows}
) of the table are part of the table border and repeated in every page.Check them in action: (credit to @igidrau for the double-line code)
Implementation details
API-related changes
stroke
field ofGridElem
andTableElem
is nowCelled<Sides<Option<Option<Arc<Stroke>>>>>
.Arc
is used in an attempt to reduce unnecessary overhead when cloning strokes.Cell
type now has astroke: Sides<Option<Arc<Stroke<Abs>>>>
field for each cell's resolved individual stroke overrides. To reflect this,GridCell
andTableCell
- exposed to the user - now havestroke: Sides<Option<Option<Arc<Stroke>>>>
fields to be able to define each cell's individual stroke override.ResolvableCell
trait, withCellGrid::resolve
receiving the grid-wide value to pass on to each cell.ResolvedCelled
type in order to be able to add#[resolve]
to the stroke fields mentioned above, because, when theCelled
is aFunc
, it has to be resolved later - when the function is actually called. So, resolving aCelled
will return aResolvedCelled
.stroke
fields are still#[fold]
as, due to Refactor folding #3294 , I could implementFold
onCelled<T>
which simply returns anotherCelled<T>
. In my implementation, actual folding only occurs when both folded values areCelled::Value
. When one is an array or a function, the innermost wins, to simplify things. (Not sure if this is the best behavior but I think it's fine. Less "magical", so to speak, I guess. But we could in theory change it so arrays can fold as well somehow.)grid.hline
,table.hline
(horizontal lines);grid.vline
andtable.vline
(vertical lines) for maximum flexibility regarding table lines.start
(default) orend
for vlines andtop
(default) orbottom
for hlines - indicates whether they appear before or after the track with their index, which is only relevant when using gutter), and stroke.Show
and thus are only usable in the context of grids and tables. Show rules, therefore, are also irrelevant for them. However, they are still affected by set rules.GridChild
andTableChild
enums (hline/vline/cell) for their children, which are translated to the internalGridItem
enum (hline/vline/cell) received byCellGrid::resolve
.y
for hlines andx
for vlines) is omitted / specified asauto
(default), the line will be placed after the last automatically placed cell in the grid. If no such cell has been placed yet in the grid, it is placed at index 0.CellGrid::resolve
, which keeps a buffer of all hlines and checks them all at the same time after placing all cells (because the amount of rows isn't known until then). Vline validation, however, is always possible (since the amount of columns is fixed) and occurs immediately after processing each vline.CellGrid::resolve
also produces an error ifend < start
for a line. Though this is kinda optional and doesn't make much of a difference.CellGrid
now has the fieldshlines
andvlines
to hold the final, resolved lines. They are vectors of vectors of lines (internalLine
type holding common line properties) - one vector for each row (hlines) and for each column (vlines), including one extra vector for lines at the maximum index (which isn't before any row, but rather after the last row).Layout changes
layout/grid/lines.rs
file and moved all line-related algorithms to it (other than rendering).generate_line_segments
as it is now used for hlines as well.generate_line_segments
was changed: it will now make a single run across a given line index to find all relevant lines with that index and resolve the final strokes for each position at that index. And, instead of producing a vector of final line segments, it now returns an iterator which yields a line segment each time it has been finished - that is, a segment starts being built as soon as a non-None
stroke is found, and it is yielded / returned to the iterator'snext
as soon as there's a track for which we have to pick a different stroke (eitherNone
or something else), which happens because a particular cell chose a different stroke, or the user placed a line override etc.render_fills_strokes
, we now push all lines to a vector before drawing them.prepend
to add each line to the frame, we have to iterate the vector in reverse so that lines appear with the correct priority.TODO
TODO:Create draft PRTODO:TestsTODO:Code cleanupTODO:Decide abouthorizontal
,vertical
APIs (delayed for now)TODO:Decide abouttable.vline(position: left | right)
ortable.vline(position: start | end)
TODO:Undraft PR