diff options
| author | Laurenz <laurmaedje@gmail.com> | 2024-04-13 10:39:45 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-04-13 08:39:45 +0000 |
| commit | 020294fca9a7065d4b9cf4e677f606ebaaa29b00 (patch) | |
| tree | c0027ad22046e2726c22298461327823d6b88d53 /tests/suite/layout/grid | |
| parent | 72dd79210602ecc799726fc096b078afbb47f299 (diff) | |
Better test runner (#3922)
Diffstat (limited to 'tests/suite/layout/grid')
| -rw-r--r-- | tests/suite/layout/grid/cell.typ | 132 | ||||
| -rw-r--r-- | tests/suite/layout/grid/colspan.typ | 142 | ||||
| -rw-r--r-- | tests/suite/layout/grid/footers.typ | 404 | ||||
| -rw-r--r-- | tests/suite/layout/grid/grid.typ | 276 | ||||
| -rw-r--r-- | tests/suite/layout/grid/headers.typ | 368 | ||||
| -rw-r--r-- | tests/suite/layout/grid/positioning.typ | 203 | ||||
| -rw-r--r-- | tests/suite/layout/grid/rowspan.typ | 490 | ||||
| -rw-r--r-- | tests/suite/layout/grid/rtl.typ | 195 | ||||
| -rw-r--r-- | tests/suite/layout/grid/stroke.typ | 435 | ||||
| -rw-r--r-- | tests/suite/layout/grid/styling.typ | 160 |
10 files changed, 2805 insertions, 0 deletions
diff --git a/tests/suite/layout/grid/cell.typ b/tests/suite/layout/grid/cell.typ new file mode 100644 index 00000000..3b08c752 --- /dev/null +++ b/tests/suite/layout/grid/cell.typ @@ -0,0 +1,132 @@ +// Test basic styling using the grid.cell element. + +--- grid-cell-override --- +// Cell override +#grid( + align: left, + fill: red, + stroke: blue, + inset: 5pt, + columns: 2, + [AAAAA], [BBBBB], + [A], [B], + grid.cell(align: right)[C], [D], + align(right)[E], [F], + align(horizon)[G], [A\ A\ A], + grid.cell(align: horizon)[G2], [A\ A\ A], + grid.cell(inset: 0pt)[I], [F], + [H], grid.cell(fill: blue)[J] +) + +--- grid-cell-show --- +// Cell show rule +#show grid.cell: it => [Zz] + +#grid( + align: left, + fill: red, + stroke: blue, + inset: 5pt, + columns: 2, + [AAAAA], [BBBBB], + [A], [B], + grid.cell(align: right)[C], [D], + align(right)[E], [F], + align(horizon)[G], [A\ A\ A] +) + +--- grid-cell-show-and-override --- +#show grid.cell: it => (it.align, it.fill) +#grid( + align: left, + row-gutter: 5pt, + [A], + grid.cell(align: right)[B], + grid.cell(fill: aqua)[B], +) + +--- grid-cell-set --- +// Cell set rules +#set grid.cell(align: center) +#show grid.cell: it => (it.align, it.fill, it.inset) +#set grid.cell(inset: 20pt) +#grid( + align: left, + row-gutter: 5pt, + [A], + grid.cell(align: right)[B], + grid.cell(fill: aqua)[B], +) + +--- grid-cell-folding --- +// Test folding per-cell properties (align and inset) +#grid( + columns: (1fr, 1fr), + rows: (2.5em, auto), + align: right, + inset: 5pt, + fill: (x, y) => (green, aqua).at(calc.rem(x + y, 2)), + [Top], grid.cell(align: bottom)[Bot], + grid.cell(inset: (bottom: 0pt))[Bot], grid.cell(inset: (bottom: 0pt))[Bot] +) + +--- grid-cell-align-override --- +// Test overriding outside alignment +#set align(bottom + right) +#grid( + columns: (1fr, 1fr), + rows: 2em, + align: auto, + fill: green, + [BR], [BR], + grid.cell(align: left, fill: aqua)[BL], grid.cell(align: top, fill: red.lighten(50%))[TR] +) + +--- grid-cell-various-overrides --- +#grid( + columns: 2, + fill: red, + align: left, + inset: 5pt, + [ABC], [ABC], + grid.cell(fill: blue)[C], [D], + grid.cell(align: center)[E], [F], + [G], grid.cell(inset: 0pt)[H] +) + +--- grid-cell-show-emph --- +#{ + show grid.cell: emph + grid( + columns: 2, + gutter: 3pt, + [Hello], [World], + [Sweet], [Italics] + ) +} + +--- grid-cell-show-based-on-position --- +// Style based on position +#{ + show grid.cell: it => { + if it.y == 0 { + strong(it) + } else if it.x == 1 { + emph(it) + } else { + it + } + } + grid( + columns: 3, + gutter: 3pt, + [Name], [Age], [Info], + [John], [52], [Nice], + [Mary], [50], [Cool], + [Jake], [49], [Epic] + ) +} + +--- table-cell-in-grid --- +// Error: 7-19 cannot use `table.cell` as a grid cell; use `grid.cell` instead +#grid(table.cell[]) diff --git a/tests/suite/layout/grid/colspan.typ b/tests/suite/layout/grid/colspan.typ new file mode 100644 index 00000000..707a9456 --- /dev/null +++ b/tests/suite/layout/grid/colspan.typ @@ -0,0 +1,142 @@ +--- grid-colspan --- +#grid( + columns: 4, + fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) }, + inset: 5pt, + align: center, + grid.cell(colspan: 4)[*Full Header*], + grid.cell(colspan: 2, fill: orange)[*Half*], + grid.cell(colspan: 2, fill: orange.darken(10%))[*Half*], + [*A*], [*B*], [*C*], [*D*], + [1], [2], [3], [4], + [5], grid.cell(colspan: 3, fill: orange.darken(10%))[6], + grid.cell(colspan: 2, fill: orange)[7], [8], [9], + [10], grid.cell(colspan: 2, fill: orange.darken(10%))[11], [12] +) + +#table( + columns: 4, + fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) }, + inset: 5pt, + align: center, + table.cell(colspan: 4)[*Full Header*], + table.cell(colspan: 2, fill: orange)[*Half*], + table.cell(colspan: 2, fill: orange.darken(10%))[*Half*], + [*A*], [*B*], [*C*], [*D*], + [1], [2], [3], [4], + [5], table.cell(colspan: 3, fill: orange.darken(10%))[6], + table.cell(colspan: 2, fill: orange)[7], [8], [9], + [10], table.cell(colspan: 2, fill: orange.darken(10%))[11], [12] +) + +--- grid-colspan-gutter --- +#grid( + columns: 4, + fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) }, + inset: 5pt, + align: center, + gutter: 3pt, + grid.cell(colspan: 4)[*Full Header*], + grid.cell(colspan: 2, fill: orange)[*Half*], + grid.cell(colspan: 2, fill: orange.darken(10%))[*Half*], + [*A*], [*B*], [*C*], [*D*], + [1], [2], [3], [4], + [5], grid.cell(colspan: 3, fill: orange.darken(10%))[6], + grid.cell(colspan: 2, fill: orange)[7], [8], [9], + [10], grid.cell(colspan: 2, fill: orange.darken(10%))[11], [12] +) + +#table( + columns: 4, + fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) }, + inset: 5pt, + align: center, + gutter: 3pt, + table.cell(colspan: 4)[*Full Header*], + table.cell(colspan: 2, fill: orange)[*Half*], + table.cell(colspan: 2, fill: orange.darken(10%))[*Half*], + [*A*], [*B*], [*C*], [*D*], + [1], [2], [3], [4], + [5], table.cell(colspan: 3, fill: orange.darken(10%))[6], + table.cell(colspan: 2, fill: orange)[7], [8], [9], + [10], table.cell(colspan: 2, fill: orange.darken(10%))[11], [12] +) + +--- grid-colspan-thick-stroke --- +#set page(width: 300pt) +#table( + columns: (2em, 2em, auto, auto), + stroke: 5pt, + [A], [B], [C], [D], + table.cell(colspan: 4, lorem(20)), + [A], table.cell(colspan: 2)[BCBCBCBC], [D] +) + +--- grid-colspan-out-of-bounds --- +// Error: 3:8-3:32 cell's colspan would cause it to exceed the available column(s) +// Hint: 3:8-3:32 try placing the cell in another position or reducing its colspan +#grid( + columns: 3, + [a], grid.cell(colspan: 3)[b] +) + +--- grid-colspan-overlap --- +// Error: 4:8-4:32 cell would span a previously placed cell at column 2, row 0 +// Hint: 4:8-4:32 try specifying your cells in a different order or reducing the cell's rowspan or colspan +#grid( + columns: 3, + grid.cell(x: 2, y: 0)[x], + [a], grid.cell(colspan: 2)[b] +) + +--- grid-colspan-over-all-fr-columns --- +// Colspan over all fractional columns shouldn't expand auto columns on finite pages +#table( + columns: (1fr, 1fr, auto), + [A], [B], [C], + [D], [E], [F] +) +#table( + columns: (1fr, 1fr, auto), + table.cell(colspan: 3, lorem(8)), + [A], [B], [C], + [D], [E], [F] +) + +--- grid-colspan-over-some-fr-columns --- +// Colspan over only some fractional columns will not trigger the heuristic, and +// the auto column will expand more than it should. The table looks off, as a result. +#table( + columns: (1fr, 1fr, auto), + [], table.cell(colspan: 2, lorem(8)), + [A], [B], [C], + [D], [E], [F] +) + +--- grid-colspan-over-all-fr-columns-page-width-auto --- +// On infinite pages, colspan over all fractional columns SHOULD expand auto columns +#set page(width: auto) +#table( + columns: (1fr, 1fr, auto), + [A], [B], [C], + [D], [E], [F] +) +#table( + columns: (1fr, 1fr, auto), + table.cell(colspan: 3, lorem(8)), + [A], [B], [C], + [D], [E], [F] +) + +--- grid-colspan-multiple-regions --- +// Test multiple regions +#set page(height: 5em) +#grid( + stroke: red, + fill: aqua, + columns: 4, + [a], [b], [c], [d], + [a], grid.cell(colspan: 2)[e, f, g, h, i], [f], + [e], [g], grid.cell(colspan: 2)[eee\ e\ e\ e], + grid.cell(colspan: 4)[eeee e e e] +) diff --git a/tests/suite/layout/grid/footers.typ b/tests/suite/layout/grid/footers.typ new file mode 100644 index 00000000..c73bcb39 --- /dev/null +++ b/tests/suite/layout/grid/footers.typ @@ -0,0 +1,404 @@ +--- grid-footer --- +#set page(width: auto, height: 15em) +#set text(6pt) +#set table(inset: 2pt, stroke: 0.5pt) +#table( + columns: 5, + align: center + horizon, + table.header( + table.cell(colspan: 5)[*Cool Zone*], + table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*], + table.hline(start: 2, end: 3, stroke: yellow) + ), + ..range(0, 5).map(i => ([John \##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten(), + table.footer( + table.hline(start: 2, end: 3, stroke: yellow), + table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*], + table.cell(colspan: 5)[*Cool Zone*] + ) +) + +--- grid-footer-gutter-and-no-repeat --- +// Gutter & no repetition +#set page(width: auto, height: 16em) +#set text(6pt) +#set table(inset: 2pt, stroke: 0.5pt) +#table( + columns: 5, + gutter: 2pt, + align: center + horizon, + table.header( + table.cell(colspan: 5)[*Cool Zone*], + table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*], + table.hline(start: 2, end: 3, stroke: yellow) + ), + ..range(0, 5).map(i => ([John \##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten(), + table.footer( + repeat: false, + table.hline(start: 2, end: 3, stroke: yellow), + table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*], + table.cell(colspan: 5)[*Cool Zone*] + ) +) + +--- grid-cell-override-in-header-and-footer --- +#table( + table.header(table.cell(stroke: red)[Hello]), + table.footer(table.cell(stroke: aqua)[Bye]), +) + +--- grid-cell-override-in-header-and-footer-with-gutter --- +#table( + gutter: 3pt, + table.header(table.cell(stroke: red)[Hello]), + table.footer(table.cell(stroke: aqua)[Bye]), +) + +--- grid-footer-top-stroke --- +// Footer's top stroke should win when repeated, but lose at the last page. +#set page(height: 10em) +#table( + stroke: green, + table.header(table.cell(stroke: red)[Hello]), + table.cell(stroke: yellow)[Hi], + table.cell(stroke: yellow)[Bye], + table.cell(stroke: yellow)[Ok], + table.footer[Bye], +) + +--- grid-footer-relative-row-sizes --- +// Relative lengths +#set page(height: 10em) +#table( + rows: (30%, 30%, auto), + [C], + [C], + table.footer[*A*][*B*], +) + +--- grid-footer-cell-with-y --- +#grid( + grid.footer(grid.cell(y: 2)[b]), + grid.cell(y: 0)[a], + grid.cell(y: 1)[c], +) + +--- grid-footer-expand --- +// Ensure footer properly expands +#grid( + columns: 2, + [a], [], + [b], [], + grid.cell(x: 1, y: 3, rowspan: 4)[b], + grid.cell(y: 2, rowspan: 2)[a], + grid.footer(), + grid.cell(y: 4)[d], + grid.cell(y: 5)[e], + grid.cell(y: 6)[f], +) + +--- grid-footer-not-at-last-row --- +// Error: 2:3-2:19 footer must end at the last row +#grid( + grid.footer([a]), + [b], +) + +--- grid-footer-not-at-last-row-two-columns --- +// Error: 3:3-3:19 footer must end at the last row +#grid( + columns: 2, + grid.footer([a]), + [b], +) + +--- grid-footer-overlap --- +// Error: 4:3-4:19 footer would conflict with a cell placed before it at column 1 row 0 +// Hint: 4:3-4:19 try reducing that cell's rowspan or moving the footer +#grid( + columns: 2, + grid.header(), + grid.footer([a]), + grid.cell(x: 1, y: 0, rowspan: 2)[a], +) + +--- grid-footer-multiple --- +// Error: 4:3-4:19 cannot have more than one footer +#grid( + [a], + grid.footer([a]), + grid.footer([b]), +) + +--- table-footer-in-grid --- +// Error: 3:3-3:20 cannot use `table.footer` as a grid footer; use `grid.footer` instead +#grid( + [a], + table.footer([a]), +) + +--- grid-footer-in-table --- +// Error: 3:3-3:19 cannot use `grid.footer` as a table footer; use `table.footer` instead +#table( + [a], + grid.footer([a]), +) + +--- grid-footer-in-grid-header --- +// Error: 14-28 cannot place a grid footer within another footer or header +#grid.header(grid.footer[a]) + +--- table-footer-in-grid-header --- +// Error: 14-29 cannot place a table footer within another footer or header +#grid.header(table.footer[a]) + +--- grid-footer-in-table-header --- +// Error: 15-29 cannot place a grid footer within another footer or header +#table.header(grid.footer[a]) + +--- table-footer-in-table-header --- +// Error: 15-30 cannot place a table footer within another footer or header +#table.header(table.footer[a]) + +--- grid-footer-in-grid-footer --- +// Error: 14-28 cannot place a grid footer within another footer or header +#grid.footer(grid.footer[a]) + +--- table-footer-in-grid-footer --- +// Error: 14-29 cannot place a table footer within another footer or header +#grid.footer(table.footer[a]) + +--- grid-footer-in-table-footer --- +// Error: 15-29 cannot place a grid footer within another footer or header +#table.footer(grid.footer[a]) + +--- table-footer-in-table-footer --- +// Error: 15-30 cannot place a table footer within another footer or header +#table.footer(table.footer[a]) + +--- grid-header-in-grid-footer --- +// Error: 14-28 cannot place a grid header within another header or footer +#grid.footer(grid.header[a]) + +--- table-header-in-grid-footer --- +// Error: 14-29 cannot place a table header within another header or footer +#grid.footer(table.header[a]) + +--- grid-header-in-table-footer --- +// Error: 15-29 cannot place a grid header within another header or footer +#table.footer(grid.header[a]) + +--- table-header-in-table-footer --- +// Error: 15-30 cannot place a table header within another header or footer +#table.footer(table.header[a]) + +--- grid-header-footer-block-with-fixed-height --- +#set page(height: 17em) +#table( + rows: (auto, 2.5em, auto), + table.header[*Hello*][*World*], + block(width: 2em, height: 10em, fill: red), + table.footer[*Bye*][*World*], +) + +--- grid-header-footer-and-rowspan-non-contiguous-1 --- +// Rowspan sizing algorithm doesn't do the best job at non-contiguous content +// ATM. +#set page(height: 20em) + +#table( + rows: (auto, 2.5em, 2em, auto, 5em, 2em, 2.5em), + table.header[*Hello*][*World*], + table.cell(rowspan: 3, lorem(20)), + table.footer[*Ok*][*Bye*], +) + +--- grid-header-footer-and-rowspan-non-contiguous-2 --- +// This should look right +#set page(height: 20em) + +#table( + rows: (auto, 2.5em, 2em, auto), + gutter: 3pt, + table.header[*Hello*][*World*], + table.cell(rowspan: 3, lorem(20)), + table.footer[*Ok*][*Bye*], +) +--- grid-header-and-footer-lack-of-space --- +// Test lack of space for header + text. +#set page(height: 9em + 2.5em + 1.5em) + +#table( + rows: (auto, 2.5em, auto, auto, 10em, 2.5em, auto), + gutter: 3pt, + table.header[*Hello*][*World*], + table.cell(rowspan: 3, lorem(30)), + table.footer[*Ok*][*Bye*], +) + +--- grid-header-and-footer-orphan-prevention --- +// Orphan header prevention test +#set page(height: 13em) +#v(8em) +#grid( + columns: 3, + gutter: 5pt, + grid.header( + [*Mui*], [*A*], grid.cell(rowspan: 2, fill: orange)[*B*], + [*Header*], [*Header* #v(0.1em)], + ), + ..([Test], [Test], [Test]) * 7, + grid.footer( + [*Mui*], [*A*], grid.cell(rowspan: 2, fill: orange)[*B*], + [*Footer*], [*Footer* #v(0.1em)], + ), +) + +--- grid-header-and-footer-empty --- +// Empty footer should just be a repeated blank row +#set page(height: 8em) +#table( + columns: 4, + align: center + horizon, + table.header(), + ..range(0, 2).map(i => ( + [John \##i], + table.cell(stroke: green)[123], + table.cell(stroke: blue)[456], + [789] + )).flatten(), + table.footer(), +) + +--- grid-header-and-footer-containing-rowspan --- +// When a footer has a rowspan with an empty row, it should be displayed +// properly +#set page(height: 14em, width: auto) + +#let count = counter("g") +#table( + rows: (auto, 2em, auto, auto), + table.header( + [eeec], + table.cell(rowspan: 2, count.step() + count.display()), + ), + [d], + block(width: 5em, fill: yellow, lorem(7)), + [d], + table.footer( + [eeec], + table.cell(rowspan: 2, count.step() + count.display()), + ) +) +#count.display() + +--- grid-nested-with-footers --- +// Nested table with footer should repeat both footers +#set page(height: 10em, width: auto) +#table( + table( + [a\ b\ c\ d], + table.footer[b], + ), + table.footer[a], +) + +--- grid-nested-footers --- +#set page(height: 12em, width: auto) +#table( + [a\ b\ c\ d], + table.footer(table( + [c], + [d], + table.footer[b], + )) +) + +--- grid-footer-rowspan --- +// General footer-only tests +#set page(height: 9em) +#table( + columns: 2, + [a], [], + [b], [], + [c], [], + [d], [], + [e], [], + table.footer( + [*Ok*], table.cell(rowspan: 2)[test], + [*Thanks*] + ) +) + +--- grid-footer-bare-1 --- +#set page(height: 5em) +#table( + table.footer[a][b][c] +) + +--- grid-footer-bare-2 --- +#table(table.footer[a][b][c]) + +#table( + gutter: 3pt, + table.footer[a][b][c] +) + +--- grid-footer-stroke-edge-cases --- +// Test footer stroke priority edge case +#set page(height: 10em) +#table( + columns: 2, + stroke: black, + ..(table.cell(stroke: aqua)[d],) * 8, + table.footer( + table.cell(rowspan: 2, colspan: 2)[a], + [c], [d] + ) +) + +--- grid-footer-hline-and-vline-1 --- +// Footer should appear at the bottom. Red line should be above the footer. +// Green line should be on the left border. +#set page(margin: 2pt) +#set text(6pt) +#table( + columns: 2, + inset: 1.5pt, + table.cell(y: 0)[a], + table.cell(x: 1, y: 1)[a], + table.cell(y: 2)[a], + table.footer( + table.hline(stroke: red), + table.vline(stroke: green), + [b], + ), + table.cell(x: 1, y: 3)[c] +) + +--- grid-footer-hline-and-vline-2 --- +// Table should be just one row. [c] appears at the third column. +#set page(margin: 2pt) +#set text(6pt) +#table( + columns: 3, + inset: 1.5pt, + table.cell(y: 0)[a], + table.footer( + table.hline(stroke: red), + table.hline(y: 1, stroke: aqua), + table.cell(y: 0)[b], + [c] + ) +) + +--- grid-footer-below-rowspans --- +// Footer should go below the rowspans. +#set page(margin: 2pt) +#set text(6pt) +#table( + columns: 2, + inset: 1.5pt, + table.cell(rowspan: 2)[a], table.cell(rowspan: 2)[b], + table.footer() +) diff --git a/tests/suite/layout/grid/grid.typ b/tests/suite/layout/grid/grid.typ new file mode 100644 index 00000000..f4f0b90a --- /dev/null +++ b/tests/suite/layout/grid/grid.typ @@ -0,0 +1,276 @@ +// Test grid layouts. + +--- grid-columns-sizings-rect --- +#let cell(width, color) = rect(width: width, height: 2cm, fill: color) +#set page(width: 100pt, height: 140pt) +#grid( + columns: (auto, 1fr, 3fr, 0.25cm, 3%, 2mm + 10%), + cell(0.5cm, rgb("2a631a")), + cell(100%, forest), + cell(100%, conifer), + cell(100%, rgb("ff0000")), + cell(100%, rgb("00ff00")), + cell(80%, rgb("00faf0")), + cell(1cm, rgb("00ff00")), + cell(0.5cm, rgb("2a631a")), + cell(100%, forest), + cell(100%, conifer), + cell(100%, rgb("ff0000")), + cell(100%, rgb("00ff00")), +) + +--- grid-gutter-fr --- +#set rect(inset: 0pt) +#grid( + columns: (auto, auto, 40%), + column-gutter: 1fr, + row-gutter: 1fr, + rect(fill: eastern)[dddaa aaa aaa], + rect(fill: conifer)[ccc], + rect(fill: rgb("dddddd"))[aaa], +) + +--- grid-row-sizing-manual-align --- +#set page(height: 3cm, margin: 0pt) +#grid( + columns: (1fr,), + rows: (1fr, auto, 2fr), + [], + align(center)[A bit more to the top], + [], +) + +--- grid-finance --- +// Test using the `grid` function to create a finance table. +#set page(width: 11cm, height: 2.5cm) +#grid( + columns: 5, + column-gutter: (2fr, 1fr, 1fr), + row-gutter: 6pt, + [*Quarter*], + [Expenditure], + [External Revenue], + [Financial ROI], + [_total_], + [*Q1*], + [173,472.57 \$], + [472,860.91 \$], + [51,286.84 \$], + [_350,675.18 \$_], + [*Q2*], + [93,382.12 \$], + [439,382.85 \$], + [-1,134.30 \$], + [_344,866.43 \$_], + [*Q3*], + [96,421.49 \$], + [238,583.54 \$], + [3,497.12 \$], + [_145,659.17 \$_], +) +// Test grid cells that overflow to the next region. + +--- grid-cell-breaking --- +#set page(width: 5cm, height: 3cm) +#grid( + columns: 2, + row-gutter: 8pt, + [Lorem ipsum dolor sit amet. + + Aenean commodo ligula eget dolor. Aenean massa. Penatibus et magnis.], + [Text that is rather short], + [Fireflies], + [Critical], + [Decorum], + [Rampage], +) + +--- grid-consecutive-rows-breaking --- +// Test a column that starts overflowing right after another row/column did +// that. +#set page(width: 5cm, height: 2cm) +#grid( + columns: 4 * (1fr,), + row-gutter: 10pt, + column-gutter: (0pt, 10%), + align(top, image("/assets/images/rhino.png")), + align(top, rect(inset: 0pt, fill: eastern, align(right)[LoL])), + [rofl], + [\ A] * 3, + [Ha!\ ] * 3, +) + +--- grid-same-row-multiple-columns-breaking --- +// Test two columns in the same row overflowing by a different amount. +#set page(width: 5cm, height: 2cm) +#grid( + columns: 3 * (1fr,), + row-gutter: 8pt, + column-gutter: (0pt, 10%), + [A], [B], [C], + [Ha!\ ] * 6, + [rofl], + [\ A] * 3, + [hello], + [darkness], + [my old] +) + +--- grid-nested-breaking --- +// Test grid within a grid, overflowing. +#set page(width: 5cm, height: 2.25cm) +#grid( + columns: 4 * (1fr,), + row-gutter: 10pt, + column-gutter: (0pt, 10%), + [A], [B], [C], [D], + grid(columns: 2, [A], [B], [C\ ]*3, [D]), + align(top, rect(inset: 0pt, fill: eastern, align(right)[LoL])), + [rofl], + [E\ ]*4, +) + +--- grid-column-sizing-auto-base --- +// Test that auto and relative columns use the correct base. +#grid( + columns: (auto, 60%), + rows: (auto, auto), + rect(width: 50%, height: 0.5cm, fill: conifer), + rect(width: 100%, height: 0.5cm, fill: eastern), + rect(width: 50%, height: 0.5cm, fill: forest), +) + +--- grid-column-sizing-fr-base --- +// Test that fr columns use the correct base. +#grid( + columns: (1fr,) * 4, + rows: (1cm,), + rect(width: 50%, fill: conifer), + rect(width: 50%, fill: forest), + rect(width: 50%, fill: conifer), + rect(width: 50%, fill: forest), +) + +--- grid-column-sizing-mixed-base --- +// Test that all three kinds of rows use the correct bases. +#set page(height: 4cm, margin: 0cm) +#grid( + rows: (1cm, 1fr, 1fr, auto), + rect(height: 50%, width: 100%, fill: conifer), + rect(height: 50%, width: 100%, fill: forest), + rect(height: 50%, width: 100%, fill: conifer), + rect(height: 25%, width: 100%, fill: forest), +) + +--- grid-trailing-linebreak-region-overflow --- +// Test that trailing linebreak doesn't overflow the region. +#set page(height: 2cm) +#grid[ + Hello \ + Hello \ + Hello \ + + World +] + +--- grid-breaking-expand-vertically --- +// Test that broken cell expands vertically. +#set page(height: 2.25cm) +#grid( + columns: 2, + gutter: 10pt, + align(bottom)[A], + [ + Top + #align(bottom)[ + Bottom \ + Bottom \ + #v(0pt) + Top + ] + ], + align(top)[B], +) + +--- grid-complete-rows --- +// Ensure grids expand enough for the given rows. +#grid( + columns: (2em, 2em), + rows: (2em,) * 4, + fill: red, + stroke: aqua, + [a] +) + +--- grid-auto-shrink --- +// Test iterative auto column shrinking. +#set page(width: 210mm - 2 * 2.5cm + 2 * 10pt) +#set text(11pt) +#table( + columns: 4, + [Hello!], + [Hello there, my friend!], + [Hello there, my friends! Hi!], + [Hello there, my friends! Hi! What is going on right now?], +) + +--- issue-grid-base-auto-row --- +// Test that grid base for auto rows makes sense. +#set page(height: 150pt) +#table( + columns: (1.5cm, auto), + rows: (auto, auto), + rect(width: 100%, fill: red), + rect(width: 100%, fill: blue), + rect(width: 100%, height: 50%, fill: green), +) + +--- issue-grid-base-auto-row-list --- +#rect(width: 100%, height: 1em) +- #rect(width: 100%, height: 1em) + - #rect(width: 100%, height: 1em) + +--- issue-grid-skip --- +// Grid now skips a remaining region when one of the cells +// doesn't fit into it at all. +#set page(height: 100pt) +#grid( + columns: (2cm, auto), + rows: (auto, auto), + rect(width: 100%, fill: red), + rect(width: 100%, fill: blue), + rect(width: 100%, height: 80%, fill: green), + [hello \ darkness #parbreak() my \ old \ friend \ I], + rect(width: 100%, height: 20%, fill: blue), + polygon(fill: red, (0%, 0%), (100%, 0%), (100%, 20%)) +) + +--- issue-grid-skip-list --- +#set page(height: 60pt) +#lorem(5) +- #lorem(5) + +--- issue-grid-double-skip --- +// Ensure that the list does not jump to the third page. +#set page(height: 70pt) +#v(40pt) +The following: ++ A ++ B + +--- issue-grid-gutter-skip --- +// Ensure gutter rows at the top or bottom of a region are skipped. +#set page(height: 10em) + +#table( + row-gutter: 1.5em, + inset: 0pt, + rows: (1fr, auto), + [a], + [], + [], + [f], + [e\ e], + [], + [a] +) diff --git a/tests/suite/layout/grid/headers.typ b/tests/suite/layout/grid/headers.typ new file mode 100644 index 00000000..b9a90461 --- /dev/null +++ b/tests/suite/layout/grid/headers.typ @@ -0,0 +1,368 @@ +--- grid-headers --- +#set page(width: auto, height: 12em) +#table( + columns: 5, + align: center + horizon, + table.header( + table.cell(colspan: 5)[*Cool Zone*], + table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*], + table.hline(start: 2, end: 3, stroke: yellow) + ), + ..range(0, 6).map(i => ([John \##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten() +) + +--- grid-headers-no-repeat --- +// Disable repetition +#set page(width: auto, height: 12em) +#table( + columns: 5, + align: center + horizon, + table.header( + table.cell(colspan: 5)[*Cool Zone*], + table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*], + table.hline(start: 2, end: 3, stroke: yellow), + repeat: false + ), + ..range(0, 6).map(i => ([John \##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten() +) + +--- grid-headers-gutter --- +#set page(width: auto, height: 12em) +#table( + columns: 5, + align: center + horizon, + gutter: 3pt, + table.header( + table.cell(colspan: 5)[*Cool Zone*], + table.cell(stroke: red)[*Name*], table.cell(stroke: aqua)[*Number*], [*Data 1*], [*Data 2*], [*Etc*], + table.hline(start: 2, end: 3, stroke: yellow), + ), + ..range(0, 6).map(i => ([John \##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten() +) + +--- grid-header-relative-row-sizes --- +// Relative lengths +#set page(height: 10em) +#table( + rows: (30%, 30%, auto), + table.header( + [*A*], + [*B*] + ), + [C], + [C] +) + +--- grid-header-cell-with-y --- +#grid( + grid.cell(y: 1)[a], + grid.header(grid.cell(y: 0)[b]), + grid.cell(y: 2)[c] +) + +--- grid-header-last-child --- +// When the header is the last grid child, it shouldn't include the gutter row +// after it, because there is none. +#grid( + columns: 2, + gutter: 3pt, + grid.header( + [a], [b], + [c], [d] + ) +) + +--- grid-header-nested --- +#set page(height: 14em) +#let t(n) = table( + columns: 3, + align: center + horizon, + gutter: 3pt, + table.header( + table.cell(colspan: 3)[*Cool Zone #n*], + [*Name*], [*Num*], [*Data*] + ), + ..range(0, 5).map(i => ([\##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456])).flatten() +) +#grid( + gutter: 3pt, + t(0), + t(1) +) + +--- grid-header-hline-and-vline --- +// Test line positioning in header +#table( + columns: 3, + stroke: none, + table.hline(stroke: red, end: 2), + table.vline(stroke: red, end: 3), + table.header( + table.hline(stroke: aqua, start: 2), + table.vline(stroke: aqua, start: 3), [*A*], table.hline(stroke: orange), table.vline(stroke: orange), [*B*], + [*C*], [*D*] + ), + [a], [b], + [c], [d], + [e], [f] +) + +--- grid-header-not-at-first-row --- +// Error: 3:3-3:19 header must start at the first row +// Hint: 3:3-3:19 remove any rows before the header +#grid( + [a], + grid.header([b]) +) + +--- grid-header-not-at-first-row-two-columns --- +// Error: 4:3-4:19 header must start at the first row +// Hint: 4:3-4:19 remove any rows before the header +#grid( + columns: 2, + [a], + grid.header([b]) +) + +--- grow-header-multiple --- +// Error: 3:3-3:19 cannot have more than one header +#grid( + grid.header([a]), + grid.header([b]), + [a], +) + +--- table-header-in-grid --- +// Error: 2:3-2:20 cannot use `table.header` as a grid header; use `grid.header` instead +#grid( + table.header([a]), + [a], +) + +--- grid-header-in-table --- +// Error: 2:3-2:19 cannot use `grid.header` as a table header; use `table.header` instead +#table( + grid.header([a]), + [a], +) + +--- grid-header-in-grid-header --- +// Error: 14-28 cannot place a grid header within another header or footer +#grid.header(grid.header[a]) + +--- table-header-in-grid-header --- +// Error: 14-29 cannot place a table header within another header or footer +#grid.header(table.header[a]) + +--- grid-header-in-table-header --- +// Error: 15-29 cannot place a grid header within another header or footer +#table.header(grid.header[a]) + +--- table-header-in-table-header --- +// Error: 15-30 cannot place a table header within another header or footer +#table.header(table.header[a]) + +--- grid-header-block-with-fixed-height --- +#set page(height: 15em) +#table( + rows: (auto, 2.5em, auto), + table.header( + [*Hello*], + [*World*] + ), + block(width: 2em, height: 20em, fill: red) +) + +--- grid-header-and-rowspan-non-contiguous-1 --- +// Rowspan sizing algorithm doesn't do the best job at non-contiguous content +// ATM. +#set page(height: 15em) + +#table( + rows: (auto, 2.5em, 2em, auto, 5em), + table.header( + [*Hello*], + [*World*] + ), + table.cell(rowspan: 3, lorem(40)) +) + +--- grid-header-and-rowspan-non-contiguous-2 --- +// Rowspan sizing algorithm doesn't do the best job at non-contiguous content +// ATM. +#set page(height: 15em) + +#table( + rows: (auto, 2.5em, 2em, auto, 5em), + gutter: 3pt, + table.header( + [*Hello*], + [*World*] + ), + table.cell(rowspan: 3, lorem(40)) +) + +--- grid-header-and-rowspan-non-contiguous-3 --- +// This should look right +#set page(height: 15em) + +#table( + rows: (auto, 2.5em, 2em, auto), + gutter: 3pt, + table.header( + [*Hello*], + [*World*] + ), + table.cell(rowspan: 3, lorem(40)) +) + +--- grid-header-lack-of-space --- +// Test lack of space for header + text. +#set page(height: 9em) + +#table( + rows: (auto, 2.5em, auto, auto, 10em), + gutter: 3pt, + table.header( + [*Hello*], + [*World*] + ), + table.cell(rowspan: 3, lorem(80)) +) + +--- grid-header-orphan-prevention --- +// Orphan header prevention test +#set page(height: 12em) +#v(8em) +#grid( + columns: 3, + grid.header( + [*Mui*], [*A*], grid.cell(rowspan: 2, fill: orange)[*B*], + [*Header*], [*Header* #v(0.1em)] + ), + ..([Test], [Test], [Test]) * 20 +) + +--- grid-header-empty --- +// Empty header should just be a repeated blank row +#set page(height: 12em) +#table( + columns: 4, + align: center + horizon, + table.header(), + ..range(0, 4).map(i => ([John \##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789])).flatten() +) + +--- grid-header-containing-rowspan --- +// When a header has a rowspan with an empty row, it should be displayed +// properly +#set page(height: 10em) + +#let count = counter("g") +#table( + rows: (auto, 2em, auto, auto), + table.header( + [eeec], + table.cell(rowspan: 2, count.step() + count.display()), + ), + [d], + block(width: 5em, fill: yellow, lorem(15)), + [d] +) +#count.display() + +--- grid-header-expand --- +// Ensure header expands to fit cell placed in it after its declaration +#set page(height: 10em) +#table( + columns: 2, + table.header( + [a], [b], + [c], + ), + table.cell(x: 1, y: 1, rowspan: 2, lorem(80)) +) + +--- grid-nested-with-headers --- +// Nested table with header should repeat both headers +#set page(height: 10em) +#table( + table.header( + [a] + ), + table( + table.header( + [b] + ), + [a\ b\ c\ d] + ) +) + +--- grid-nested-headers --- +#set page(height: 12em) +#table( + table.header( + table( + table.header( + [b] + ), + [c], + [d] + ) + ), + [a\ b] +) + +--- grid-header-stroke-edge-cases --- +// Test header stroke priority edge case (last header row removed) +#set page(height: 8em) +#table( + columns: 2, + stroke: black, + gutter: (auto, 3pt), + table.header( + [c], [d], + ), + ..(table.cell(stroke: aqua)[d],) * 8, +) + +--- grid-header-hline-bottom --- +// Yellow line should be kept here +#set text(6pt) +#table( + column-gutter: 3pt, + inset: 1pt, + table.header( + [a], + table.hline(stroke: yellow), + ), + table.cell(rowspan: 2)[b] +) + +--- grid-header-hline-bottom-manually --- +// Red line should be kept here +#set page(height: 6em) +#set text(6pt) +#table( + column-gutter: 3pt, + inset: 1pt, + table.header( + table.hline(stroke: red, position: bottom), + [a], + ), + [a], + table.cell(stroke: aqua)[b] +) + +--- grid-header-rowspan-base --- +#set page(height: 7em) +#set text(6pt) +#let full-block = block(width: 2em, height: 100%, fill: red) +#table( + columns: 3, + inset: 1.5pt, + table.header( + [a], full-block, table.cell(rowspan: 2, full-block), + [b] + ) +) diff --git a/tests/suite/layout/grid/positioning.typ b/tests/suite/layout/grid/positioning.typ new file mode 100644 index 00000000..31896d99 --- /dev/null +++ b/tests/suite/layout/grid/positioning.typ @@ -0,0 +1,203 @@ +// Test cell positioning in grids. + +--- grid-cell-show-x-y --- +#{ + show grid.cell: it => (it.x, it.y) + grid( + columns: 2, + inset: 5pt, + fill: aqua, + gutter: 3pt, + [Hello], [World], + [Sweet], [Home] + ) +} +#{ + show table.cell: it => pad(rest: it.inset)[#(it.x, it.y)] + table( + columns: 2, + gutter: 3pt, + [Hello], [World], + [Sweet], [Home] + ) +} + +--- grid-cell-position-out-of-order --- +// Positioning cells in a different order than they appear +#grid( + columns: 2, + [A], [B], + grid.cell(x: 1, y: 2)[C], grid.cell(x: 0, y: 2)[D], + grid.cell(x: 1, y: 1)[E], grid.cell(x: 0, y: 1)[F], +) + +--- grid-cell-position-extra-rows --- +// Creating more rows by positioning out of bounds +#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-cell-position-collide --- +// Error: 3:3-3:42 attempted to place a second cell at column 0, row 0 +// Hint: 3:3-3:42 try specifying your cells in a different order +#grid( + [A], + grid.cell(x: 0, y: 0)[This shall error] +) + +--- table-cell-position-collide --- +// Error: 3:3-3:43 attempted to place a second cell at column 0, row 0 +// Hint: 3:3-3:43 try specifying your cells in a different order +#table( + [A], + table.cell(x: 0, y: 0)[This shall error] +) + +--- grid-cell-position-automatic-skip-manual --- +// Automatic position cell skips custom position cell +#grid( + grid.cell(x: 0, y: 0)[This shall not error], + [A] +) + +--- grid-cell-position-x-out-of-bounds --- +// Error: 4:3-4:36 cell could not be placed at invalid column 2 +#grid( + columns: 2, + [A], + grid.cell(x: 2)[This shall error] +) + +--- grid-cell-position-partial --- +// Partial positioning +#grid( + columns: 3, + rows: 1.5em, + inset: 5pt, + fill: aqua, + [A], grid.cell(y: 1, fill: green)[B], [C], grid.cell(x: auto, y: 1, fill: green)[D], [E], + grid.cell(y: 2, fill: green)[F], grid.cell(x: 0, fill: orange)[G], grid.cell(x: 0, y: auto, fill: orange)[H], + grid.cell(x: 1, fill: orange)[I] +) + +#table( + columns: 3, + rows: 1.5em, + inset: 5pt, + fill: aqua, + [A], table.cell(y: 1, fill: green)[B], [C], table.cell(x: auto, y: 1, fill: green)[D], [E], + table.cell(y: 2, fill: green)[F], table.cell(x: 0, fill: orange)[G], table.cell(x: 0, y: auto, fill: orange)[H], + table.cell(x: 1, fill: orange)[I] +) + +--- grid-cell-position-partial-collide --- +// Error: 4:3-4:21 cell could not be placed in row 0 because it was full +// Hint: 4:3-4:21 try specifying your cells in a different order +#grid( + columns: 2, + [A], [B], + grid.cell(y: 0)[C] +) + +--- table-cell-position-partial-collide --- +// Error: 4:3-4:22 cell could not be placed in row 0 because it was full +// Hint: 4:3-4:22 try specifying your cells in a different order +#table( + columns: 2, + [A], [B], + table.cell(y: 0)[C] +) + +--- grid-calendar --- +#set page(width: auto) +#show grid.cell: it => { + if it.y == 0 { + set text(white) + 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( + fill: (x, y) => if y == 0 { gray.darken(50%) }, + columns: (30pt,) * 7, + rows: (auto, 30pt), + // Events will be written at the bottom of each day square. + align: bottom, + inset: 5pt, + stroke: (thickness: 0.5pt, dash: "densely-dotted"), + + [Sun], [Mon], [Tue], [Wed], [Thu], [Fri], [Sat], + + // This event will occur on the first Friday (sixth column). + grid.cell(x: 5, fill: yellow.darken(10%))[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, fill: red.lighten(50%))[Meet],) * 5, + + // This event will occur at day 19. + grid.cell(x: 4, y: 3, fill: orange.lighten(25%))[Talk], + + // These events will occur at the second week, where available. + grid.cell(y: 2, fill: aqua)[Chat], + grid.cell(y: 2, fill: aqua)[Walk], +) + +--- grid-exam --- +#set page(width: auto) +#show table.cell: it => { + if it.x == 0 or it.y == 0 { + set text(white) + strong(it) + } else if it.body == [] { + // Replace empty cells with 'N/A' + pad(rest: it.inset)[_N/A_] + } else { + it + } +} + +#table( + fill: (x, y) => if x == 0 or y == 0 { gray.darken(50%) }, + columns: 4, + [], [Exam 1], [Exam 2], [Exam 3], + ..([John], [Mary], [Jake], [Robert]).map(table.cell.with(x: 0)), + + // Mary got grade A on Exam 3. + table.cell(x: 3, y: 2, fill: green)[A], + + // Everyone got grade A on Exam 2. + ..(table.cell(x: 2, fill: green)[A],) * 4, + + // Robert got grade B on other exams. + ..(table.cell(y: 4, fill: aqua)[B],) * 2, +) diff --git a/tests/suite/layout/grid/rowspan.typ b/tests/suite/layout/grid/rowspan.typ new file mode 100644 index 00000000..f7a377b6 --- /dev/null +++ b/tests/suite/layout/grid/rowspan.typ @@ -0,0 +1,490 @@ +--- grid-rowspan --- +#grid( + columns: 4, + fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) }, + inset: 5pt, + align: center, + grid.cell(rowspan: 2, fill: orange)[*Left*], + [Right A], [Right A], [Right A], + [Right B], grid.cell(colspan: 2, rowspan: 2, fill: orange.darken(10%))[B Wide], + [Left A], [Left A], + [Left B], [Left B], grid.cell(colspan: 2, rowspan: 3, fill: orange)[Wide and Long] +) + +#table( + columns: 4, + fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) }, + inset: 5pt, + align: center, + table.cell(rowspan: 2, fill: orange)[*Left*], + [Right A], [Right A], [Right A], + [Right B], table.cell(colspan: 2, rowspan: 2, fill: orange.darken(10%))[B Wide], + [Left A], [Left A], + [Left B], [Left B], table.cell(colspan: 2, rowspan: 3, fill: orange)[Wide and Long] +) + +--- grid-rowspan-gutter --- +#grid( + columns: 4, + fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) }, + inset: 5pt, + align: center, + gutter: 3pt, + grid.cell(rowspan: 2, fill: orange)[*Left*], + [Right A], [Right A], [Right A], + [Right B], grid.cell(colspan: 2, rowspan: 2, fill: orange.darken(10%))[B Wide], + [Left A], [Left A], + [Left B], [Left B], grid.cell(colspan: 2, rowspan: 3, fill: orange)[Wide and Long] +) + +#table( + columns: 4, + fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) }, + inset: 5pt, + align: center, + gutter: 3pt, + table.cell(rowspan: 2, fill: orange)[*Left*], + [Right A], [Right A], [Right A], + [Right B], table.cell(colspan: 2, rowspan: 2, fill: orange.darken(10%))[B Wide], + [Left A], [Left A], + [Left B], [Left B], table.cell(colspan: 2, rowspan: 3, fill: orange)[Wide and Long] +) + +--- grid-rowspan-fixed-size --- +// Fixed-size rows +#set page(height: 10em) +#grid( + columns: 2, + rows: 1.5em, + fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) }, + grid.cell(rowspan: 3)[R1], [b], + [c], + [d], + [e], [f], + grid.cell(rowspan: 5)[R2], [h], + [i], + [j], + [k], + [l], + [m], [n] +) + +--- grid-rowspan-cell-coordinates --- +// Cell coordinate tests +#set page(height: 10em) +#show table.cell: it => [(#it.x, #it.y)] +#table( + columns: 3, + fill: red, + [a], [b], table.cell(rowspan: 2)[c], + table.cell(colspan: 2)[d], + table.cell(colspan: 3, rowspan: 10)[a], + table.cell(colspan: 2)[b], +) +#table( + columns: 3, + gutter: 3pt, + fill: red, + [a], [b], table.cell(rowspan: 2)[c], + table.cell(colspan: 2)[d], + table.cell(colspan: 3, rowspan: 9)[a], + table.cell(colspan: 2)[b], +) + +--- grid-rowspan-over-auto-row --- +// Auto row expansion +#set page(height: 10em) +#grid( + columns: (1em, 1em), + rows: (0.5em, 0.5em, auto), + fill: orange, + gutter: 3pt, + grid.cell(rowspan: 4, [x x x x] + place(bottom)[*Bot*]), + [a], + [b], + [c], + [d] +) + +--- grid-rowspan-excessive --- +// Excessive rowspan (no gutter) +#set page(height: 10em) +#table( + columns: 4, + fill: red, + [a], [b], table.cell(rowspan: 2)[c], [d], + table.cell(colspan: 2, stroke: (bottom: aqua + 2pt))[e], table.cell(stroke: (bottom: aqua))[f], + table.cell(colspan: 2, rowspan: 10)[R1], table.cell(colspan: 2, rowspan: 10)[R2], + [b], +) + +--- grid-rowspan-excessive-gutter --- +// Excessive rowspan (with gutter) +#set page(height: 10em) +#table( + columns: 4, + gutter: 3pt, + fill: red, + [a], [b], table.cell(rowspan: 2)[c], [d], + table.cell(colspan: 2, stroke: (bottom: aqua + 2pt))[e], table.cell(stroke: (bottom: aqua))[f], + table.cell(colspan: 2, rowspan: 10)[R1], table.cell(colspan: 2, rowspan: 10)[R2], + [b], +) + +--- grid-rowspan-over-fr-row-at-end --- +// Fractional rows +// They cause the auto row to expand more than needed. +#set page(height: 10em) +#grid( + fill: red, + gutter: 3pt, + columns: 3, + rows: (1em, auto, 1fr), + [a], [b], grid.cell(rowspan: 3, block(height: 4em, width: 1em, fill: orange)), + [c], [d], + [e], [f] +) + +--- grid-rowspan-over-fr-row-at-start --- +// Fractional rows +#set page(height: 10em) +#grid( + fill: red, + gutter: 3pt, + columns: 3, + rows: (1fr, auto, 1em), + [a], [b], grid.cell(rowspan: 3, block(height: 4em, width: 1em, fill: orange)), + [c], [d], + [e], [f] +) + +--- grid-rowspan-cell-order --- +// Cell order +#let count = counter("count") +#show grid.cell: it => { + count.step() + count.display() +} + +#grid( + columns: (2em,) * 3, + stroke: aqua, + rows: 1.2em, + fill: (x, y) => if calc.odd(x + y) { red } else { orange }, + [a], grid.cell(rowspan: 2)[b], grid.cell(rowspan: 2)[c], + [d], + grid.cell(rowspan: 2)[f], [g], [h], + [i], [j], + [k], [l], [m], + grid.cell(rowspan: 2)[n], [o], [p], + [q], [r], + [s], [t], [u] +) + +--- grid-rowspan-unbreakable-1 --- +#table( + columns: 3, + rows: (auto, auto, auto, 2em), + gutter: 3pt, + table.cell(rowspan: 4)[a \ b\ c\ d\ e], [c], [d], + [e], table.cell(breakable: false, rowspan: 2)[f], + [g] +) + +--- grid-rowspan-unbreakable-2 --- +// Test cell breakability +#show grid.cell: it => { + assert.eq(it.breakable, (it.x, it.y) != (0, 6) and (it.y in (2, 5, 6) or (it.x, it.y) in ((0, 1), (2, 3), (1, 7)))) + it.breakable +} +#grid( + columns: 3, + rows: (6pt, 1fr, auto, 1%, 1em, auto, auto, 0.2in), + row-gutter: (0pt, 0pt, 0pt, auto), + [a], [b], [c], + grid.cell(rowspan: 3)[d], [e], [f], + [g], [h], + [i], grid.cell(rowspan: 2)[j], + [k], + grid.cell(y: 5)[l], + grid.cell(y: 6, breakable: false)[m], grid.cell(y: 6, breakable: true)[n], + grid.cell(y: 7, breakable: false)[o], grid.cell(y: 7, breakable: true)[p], grid.cell(y: 7, breakable: auto)[q] +) + +--- grid-rowspan-in-all-columns-stroke --- +#table( + columns: 2, + table.cell(stroke: (bottom: red))[a], [b], + table.hline(stroke: green), + table.cell(stroke: (top: yellow, left: green, right: aqua, bottom: blue), colspan: 1, rowspan: 2)[d], table.cell(colspan: 1, rowspan: 2)[e], + [f], + [g] +) + +--- grid-rowspan-in-all-columns-stroke-gutter --- +#table( + columns: 2, + gutter: 3pt, + table.cell(stroke: (bottom: red))[a], [b], + table.hline(stroke: green), + table.cell(stroke: (top: yellow, left: green, right: aqua, bottom: blue), colspan: 1, rowspan: 2)[d], table.cell(colspan: 1, rowspan: 2)[e], + [f], + [g] +) + +--- grid-rowspan-block-full-height --- +// Block below shouldn't expand to the end of the page, but stay within its +// rows' boundaries. +#set page(height: 9em) +#table( + rows: (1em, 1em, 1fr, 1fr, auto), + table.cell(rowspan: 2, block(width: 2em, height: 100%, fill: red)), + table.cell(rowspan: 2, block(width: 2em, height: 100%, fill: red)), + [a] +) + +--- grid-rowspan-block-overflow --- +#set page(height: 7em) +#table( + columns: 3, + [], [], table.cell(breakable: true, rowspan: 2, block(width: 2em, height: 100%, fill: red)), + table.cell(breakable: false, block(width: 2em, height: 100%, fill: red)), + table.cell(breakable: false, rowspan: 2, block(width: 2em, height: 100%, fill: red)), +) + +// Rowspan split tests + +--- grid-rowspan-split-1 --- +#set page(height: 10em) +#table( + columns: 2, + rows: (auto, auto, 3em), + fill: red, + [a], table.cell(rowspan: 3, block(width: 50%, height: 10em, fill: orange) + place(bottom)[*ZD*]), + [e], + [f] +) + +--- grid-rowspan-split-2 --- +#set page(height: 10em) +#table( + columns: 2, + rows: (auto, auto, 3em), + row-gutter: 1em, + fill: red, + [a], table.cell(rowspan: 3, block(width: 50%, height: 10em, fill: orange) + place(bottom)[*ZD*]), + [e], + [f] +) + +--- grid-rowspan-split-3 --- +#set page(height: 5em) +#table( + columns: 2, + fill: red, + inset: 0pt, + table.cell(fill: orange, rowspan: 10, place(bottom)[*Z*] + [x\ ] * 10 + place(bottom)[*ZZ*]), + ..([y],) * 10, + [a], [b], +) + +--- grid-rowspan-split-4 --- +#set page(height: 5em) +#table( + columns: 2, + fill: red, + inset: 0pt, + gutter: 2pt, + table.cell(fill: orange, rowspan: 10, place(bottom)[*Z*] + [x\ ] * 10 + place(bottom)[*ZZ*]), + ..([y],) * 10, + [a], [b], +) + +--- grid-rowspan-split-5 --- +#set page(height: 5em) +#table( + columns: 2, + fill: red, + inset: 0pt, + table.cell(fill: orange, rowspan: 10, breakable: false, place(bottom)[*Z*] + [x\ ] * 10 + place(bottom)[*ZZ*]), + ..([y],) * 10, + [a], [b], +) + +--- grid-rowspan-split-6 --- +#set page(height: 5em) +#table( + columns: 2, + fill: red, + inset: 0pt, + gutter: 2pt, + table.cell(fill: orange, rowspan: 10, breakable: false, place(bottom)[*Z*] + [x\ ] * 10 + place(bottom)[*ZZ*]), + ..([y],) * 10, + [a], [b], +) + +--- grid-rowspan-split-7 --- +#set page(height: 5em) +#grid( + columns: 2, + stroke: red, + inset: 5pt, + grid.cell(rowspan: 5)[a\ b\ c\ d\ e] +) + +--- grid-rowspan-split-8 --- +#set page(height: 5em) +#table( + columns: 2, + gutter: 3pt, + stroke: red, + inset: 5pt, + table.cell(rowspan: 5)[a\ b\ c\ d\ e] +) + +// Rowspan split without ending at the auto row + +--- grid-rowspan-split-9 --- +#set page(height: 6em) +#table( + rows: (4em,) * 7 + (auto,) + (4em,) * 7, + columns: 2, + column-gutter: 1em, + row-gutter: (1em, 2em) * 4, + fill: (x, y) => if calc.odd(x + y) { orange.lighten(20%) } else { red }, + table.cell(rowspan: 15, [a \ ] * 15), + [] * 15 +) + +--- grid-rowspan-split-10 --- +#set page(height: 6em) +#table( + rows: (4em,) * 7 + (auto,) + (4em,) * 7, + columns: 2, + column-gutter: 1em, + row-gutter: (1em, 2em) * 4, + fill: (x, y) => if calc.odd(x + y) { green } else { green.darken(40%) }, + table.cell(rowspan: 15, block(fill: blue, width: 2em, height: 4em * 14 + 3em)), + [] * 15 +) + +--- grid-rowspan-split-11 --- +#set page(height: 6em) +#table( + rows: (3em,) * 15, + columns: 2, + column-gutter: 1em, + row-gutter: (1em, 2em) * 4, + fill: (x, y) => if calc.odd(x + y) { aqua } else { blue }, + table.cell(breakable: true, rowspan: 15, [a \ ] * 15), + [] * 15 +) + +// Some splitting corner cases + +--- grid-rowspan-split-12 --- +// Inside the larger rowspan's range, there's an unbreakable rowspan and a +// breakable rowspan. This should work normally. +// The auto row will also expand ignoring the last fractional row. +#set page(height: 10em) +#table( + gutter: 0.5em, + columns: 2, + rows: (2em,) * 10 + (auto, auto, 2em, 1fr), + fill: (_, y) => if calc.even(y) { aqua } else { blue }, + table.cell(rowspan: 14, block(width: 2em, height: 2em * 10 + 2em + 5em, fill: red)[]), + ..([a],) * 5, + table.cell(rowspan: 3)[a\ b], + table.cell(rowspan: 5, [a\ b\ c\ d\ e\ f\ g\ h]), + [z] +) + +--- grid-rowspan-split-13 --- +// Inset moving to next region bug +#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], +) + +--- grid-rowspan-split-14 --- +// Second lorem must be sent to the next page, too big +#set page(width: 10cm, height: 9cm, 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], +) + +--- grid-rowspan-split-15 --- +// Auto row must expand properly in both cases +#set text(10pt) +#show table.cell: it => if it.x == 0 { it } else { layout(size => size.height) } +#table( + columns: 2, + rows: (1em, auto, 2em, 3em, 4em), + gutter: 3pt, + table.cell(rowspan: 5, block(fill: orange, height: 15em)[a]), + [b], + [c], + [d], + [e], + [f] +) + +#table( + columns: 2, + rows: (1em, auto, 2em, 3em, 4em), + gutter: 3pt, + table.cell(rowspan: 5, breakable: false, block(fill: orange, height: 15em)[a]), + [b], + [c], + [d], + [e], + [f] +) + +--- grid-rowspan-split-16 --- +// Expanding on unbreakable auto row +#set page(height: 7em, margin: (bottom: 2em)) +#grid( + columns: 2, + rows: (1em, 1em, auto, 1em, 1em, 1em), + fill: (x, y) => if x == 0 { aqua } else { blue }, + stroke: black, + gutter: 2pt, + grid.cell(rowspan: 5, block(height: 10em)[a]), + [a], + [b], + grid.cell(breakable: false, v(3em) + [c]), + [d], + [e], + [f], [g] +) + +--- grid-rowspan-split-17 --- +#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 +) diff --git a/tests/suite/layout/grid/rtl.typ b/tests/suite/layout/grid/rtl.typ new file mode 100644 index 00000000..7c0e999a --- /dev/null +++ b/tests/suite/layout/grid/rtl.typ @@ -0,0 +1,195 @@ +// Test RTL grid. + +--- list-rtl --- +#set text(dir: rtl) +- מימין לשמאל + +--- grid-rtl --- +#set text(dir: rtl) +#table(columns: 2)[A][B][C][D] + +--- grid-rtl-colspan --- +// Test interaction between RTL and colspans +#set text(dir: rtl) +#grid( + columns: 4, + fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) }, + inset: 5pt, + align: center, + grid.cell(colspan: 4)[*Full Header*], + grid.cell(colspan: 2, fill: orange)[*Half*], + grid.cell(colspan: 2, fill: orange.darken(10%))[*Half*], + [*A*], [*B*], [*C*], [*D*], + [1], [2], [3], [4], + [5], grid.cell(colspan: 3, fill: orange.darken(10%))[6], + grid.cell(colspan: 2, fill: orange)[7], [8], [9], + [10], grid.cell(colspan: 2, fill: orange.darken(10%))[11], [12] +) + +#grid( + columns: 4, + fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) }, + inset: 5pt, + align: center, + gutter: 3pt, + grid.cell(colspan: 4)[*Full Header*], + grid.cell(colspan: 2, fill: orange)[*Half*], + grid.cell(colspan: 2, fill: orange.darken(10%))[*Half*], + [*A*], [*B*], [*C*], [*D*], + [1], [2], [3], [4], + [5], grid.cell(colspan: 3, fill: orange.darken(10%))[6], + grid.cell(colspan: 2, fill: orange)[7], [8], [9], + [10], grid.cell(colspan: 2, fill: orange.darken(10%))[11], [12] +) + +--- grid-rtl-colspan-stroke --- +#set text(dir: rtl) +#table( + columns: 4, + fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) }, + inset: 5pt, + align: center, + table.cell(colspan: 4)[*Full Header*], + table.cell(colspan: 2, fill: orange)[*Half*], + table.cell(colspan: 2, fill: orange.darken(10%))[*Half*], + [*A*], [*B*], [*C*], [*D*], + [1], [2], [3], [4], + [5], table.cell(colspan: 3, fill: orange.darken(10%))[6], + table.cell(colspan: 2, fill: orange)[7], [8], [9], + [10], table.cell(colspan: 2, fill: orange.darken(10%))[11], [12] +) + +#table( + columns: 4, + fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) }, + inset: 5pt, + align: center, + gutter: 3pt, + table.cell(colspan: 4)[*Full Header*], + table.cell(colspan: 2, fill: orange)[*Half*], + table.cell(colspan: 2, fill: orange.darken(10%))[*Half*], + [*A*], [*B*], [*C*], [*D*], + [1], [2], [3], [4], + [5], table.cell(colspan: 3, fill: orange.darken(10%))[6], + table.cell(colspan: 2, fill: orange)[7], [8], [9], + [10], table.cell(colspan: 2, fill: orange.darken(10%))[11], [12] +) + +--- grid-rtl-multiple-regions --- +// Test multiple regions +#set page(height: 5em) +#set text(dir: rtl) +#grid( + stroke: red, + fill: aqua, + columns: 4, + [a], [b], [c], [d], + [a], grid.cell(colspan: 2)[e, f, g, h, i], [f], + [e], [g], grid.cell(colspan: 2)[eee\ e\ e\ e], + grid.cell(colspan: 4)[eeee e e e] +) + +--- grid-rtl-vline-position --- +// Test left and right for vlines in RTL +#set text(dir: rtl) +#grid( + columns: 3, + inset: 5pt, + grid.vline(stroke: red, position: left), grid.vline(stroke: green, position: right), [a], + grid.vline(stroke: red, position: left), grid.vline(stroke: 2pt, position: right), [b], + grid.vline(stroke: red, position: left), grid.vline(stroke: 2pt, position: right), [c], + grid.vline(stroke: aqua, position: right) +) + +#grid( + columns: 3, + inset: 5pt, + gutter: 3pt, + grid.vline(stroke: green, position: left), grid.vline(stroke: red, position: right), [a], + grid.vline(stroke: blue, position: left), grid.vline(stroke: red, position: right), [b], + grid.vline(stroke: blue, position: left), grid.vline(stroke: red, position: right), [c], + grid.vline(stroke: 2pt, position: right) +) + +#grid( + columns: 3, + inset: 5pt, + grid.vline(stroke: green, position: start), grid.vline(stroke: red, position: end), [a], + grid.vline(stroke: 2pt, position: start), grid.vline(stroke: red, position: end), [b], + grid.vline(stroke: 2pt, position: start), grid.vline(stroke: red, position: end), [c], + grid.vline(stroke: 2pt, position: start) +) + +#grid( + columns: 3, + inset: 5pt, + gutter: 3pt, + grid.vline(stroke: green, position: start), grid.vline(stroke: red, position: end), [a], + grid.vline(stroke: blue, position: start), grid.vline(stroke: red, position: end), [b], + grid.vline(stroke: blue, position: start), grid.vline(stroke: red, position: end), [c], + grid.vline(stroke: 2pt, position: start) +) + +--- grid-rtl-vline-out-of-bounds --- +// Error: 3:8-3:34 cannot place vertical line at the 'end' position of the end border (x = 1) +// Hint: 3:8-3:34 set the line's position to 'start' or place it at a smaller 'x' index +#set text(dir: rtl) +#grid( + [a], grid.vline(position: left) +) + +--- grid-rtl-complex --- +#set text(dir: rtl) + +#grid( + columns: 4, + fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) }, + inset: 5pt, + align: center, + grid.cell(rowspan: 2, fill: orange)[*Left*], + [Right A], [Right A], [Right A], + [Right B], grid.cell(colspan: 2, rowspan: 2, fill: orange.darken(10%))[B Wide], + [Left A], [Left A], + [Left B], [Left B], grid.cell(colspan: 2, rowspan: 3, fill: orange)[Wide and Long] +) + +#table( + columns: 4, + fill: (x, y) => if calc.odd(x + y) { blue.lighten(50%) } else { blue.lighten(10%) }, + inset: 5pt, + align: center, + gutter: 3pt, + table.cell(rowspan: 2, fill: orange)[*Left*], + [Right A], [Right A], [Right A], + [Right B], table.cell(colspan: 2, rowspan: 2, fill: orange.darken(10%))[B Wide], + [Left A], [Left A], + [Left B], [Left B], table.cell(colspan: 2, rowspan: 3, fill: orange)[Wide and Long] +) + +--- grid-rtl-rowspan --- +#set page(height: 10em) +#set text(dir: rtl) +#table( + columns: 2, + rows: (auto, auto, 3em), + row-gutter: 1em, + fill: red, + [a], table.cell(rowspan: 3, block(width: 50%, height: 10em, fill: orange) + place(bottom)[*ZD*]), + [e], + [f] +) + +--- grid-rtl-header --- +// Headers +#set page(height: 15em) +#set text(dir: rtl) +#table( + columns: 5, + align: center + horizon, + table.header( + table.cell(colspan: 5)[*Cool Zone*], + table.cell(stroke: red)[*N1*], table.cell(stroke: aqua)[*N2*], [*D1*], [*D2*], [*Etc*], + table.hline(start: 2, end: 3, stroke: yellow) + ), + ..range(0, 10).map(i => ([\##i], table.cell(stroke: green)[123], table.cell(stroke: blue)[456], [789], [?], table.hline(start: 4, end: 5, stroke: red))).flatten() +) diff --git a/tests/suite/layout/grid/stroke.typ b/tests/suite/layout/grid/stroke.typ new file mode 100644 index 00000000..9c1c3482 --- /dev/null +++ b/tests/suite/layout/grid/stroke.typ @@ -0,0 +1,435 @@ +--- grid-stroke-pattern --- +#let double-line = pattern(size: (1.5pt, 1.5pt), { + place(line(stroke: .6pt, start: (0%, 50%), end: (100%, 50%))) +}) + +#table( + stroke: (_, y) => if y != 1 { (bottom: black) }, + columns: 3, + table.cell(colspan: 3, align: center)[*Epic Table*], + align(center)[*Name*], align(center)[*Age*], align(center)[*Data*], + table.hline(stroke: (paint: double-line, thickness: 2pt)), + [John], [30], [None], + [Martha], [20], [A], + [Joseph], [35], [D] +) + +--- grid-stroke-folding --- +// Test folding +#set grid(stroke: red) +#set grid(stroke: 5pt) + +#grid( + inset: 10pt, + columns: 2, + stroke: stroke(dash: "loosely-dotted"), + grid.vline(start: 2, end: 3, stroke: (paint: green, dash: none)), + [a], [b], + grid.hline(end: 1, stroke: blue), + [c], [d], + [e], grid.cell(stroke: aqua)[f] +) + +--- grid-stroke-set-on-cell-and-line --- +// Test set rules on cells and folding +#set table.cell(stroke: 4pt) +#set table.cell(stroke: blue) +#set table.hline(stroke: red) +#set table.hline(stroke: 0.75pt) +#set table.vline(stroke: 0.75pt) +#set table.vline(stroke: aqua) + +#table( + columns: 3, + gutter: 3pt, + inset: 5pt, + [a], [b], table.vline(position: end), [c], + [d], [e], [f], + table.hline(position: bottom), + [g], [h], [i], +) + +--- grid-stroke-field-in-show --- +// Test stroke field on cell show rules +#set grid.cell(stroke: (x: 4pt)) +#set grid.cell(stroke: (x: blue)) +#show grid.cell: it => { + test(it.stroke, (left: stroke(paint: blue, thickness: 4pt, dash: "loosely-dotted"), right: blue + 4pt, top: stroke(thickness: 1pt), bottom: none)) + it +} +#grid( + stroke: (left: (dash: "loosely-dotted")), + inset: 5pt, + grid.hline(stroke: red), + grid.cell(stroke: (top: 1pt))[a], grid.vline(stroke: yellow), +) + +--- grid-stroke-complex --- +#table( + columns: 3, + [a], table.cell(colspan: 2)[b c], + table.cell(stroke: blue)[d], [e], [f], + [g], [h], table.cell(stroke: (left: yellow, top: green, right: aqua, bottom: red))[i], + [j], [k], [l], + table.cell(stroke: 3pt)[m], [n], table.cell(stroke: (dash: "loosely-dotted"))[o], +) + +--- grid-stroke-array --- +// Test per-column stroke array +#let t = table( + columns: 3, + stroke: (red, blue, green), + [a], [b], [c], + [d], [e], [f], + [h], [i], [j], +) +#t +#set text(dir: rtl) +#t + +--- grid-stroke-func --- +#grid( + columns: 3, + inset: 3pt, + stroke: (x, _) => (right: (5pt, (dash: "dotted")).at(calc.rem(x, 2)), bottom: (dash: "densely-dotted")), + grid.vline(x: 0, stroke: red), + grid.vline(x: 1, stroke: red), + grid.vline(x: 2, stroke: red), + grid.vline(x: 3, stroke: red), + grid.hline(y: 0, end: 1, stroke: blue), + grid.hline(y: 1, end: 1, stroke: blue), + grid.cell[a], + [b], [c] +) + +--- grid-stroke-manually-positioned-lines --- +#set page(height: 5em) +#table( + columns: 3, + inset: 3pt, + table.hline(y: 0, end: none, stroke: 3pt + blue), + table.vline(x: 0, end: none, stroke: 3pt + green), + table.hline(y: 5, end: none, stroke: 3pt + red), + table.vline(x: 3, end: none, stroke: 3pt + yellow), + [a], [b], [c], + [a], [b], [c], + [a], [b], [c], + [a], [b], [c], + [a], [b], [c], +) + +--- grid-stroke-automatically-positioned-lines --- +// Automatically positioned lines +// Plus stroke thickness ordering +#table( + columns: 3, + table.hline(stroke: red + 5pt), + table.vline(stroke: blue + 5pt), + table.vline(stroke: 2pt), + [a], + table.vline(x: 1, stroke: aqua + 5pt), + [b], + table.vline(stroke: aqua + 5pt), + [c], + table.vline(stroke: yellow + 5.2pt), + table.hline(stroke: green + 5pt), + [a], [b], [c], + [a], table.hline(stroke: green + 2pt), table.vline(stroke: 2pt), [b], [c], +) + +--- grid-stroke-priority-line --- +// Line specification order priority +// The last line should be blue, not red. +// The middle aqua line should be gone due to the 'none' override. +#grid( + columns: 2, + inset: 2pt, + grid.hline(y: 2, stroke: red + 5pt), + grid.vline(), + [a], [b], + grid.hline(stroke: red), + grid.hline(stroke: none), + [c], grid.cell(stroke: (top: aqua))[d], + grid.hline(stroke: blue), +) + +--- grid-stroke-hline-position-bottom-gutter --- +// Position: bottom and position: end with gutter should have a visible effect +// of moving the lines after the next track. +#table( + columns: 3, + gutter: 3pt, + stroke: blue, + table.hline(end: 2, stroke: red), + table.hline(end: 2, stroke: aqua, position: bottom), + table.vline(end: 2, stroke: green), [a], table.vline(end: 2, stroke: green), table.vline(end: 2, position: end, stroke: orange), [b], table.vline(end: 2, stroke: aqua, position: end), table.vline(end: 2, stroke: green), [c], table.vline(end: 2, stroke: green), + [d], [e], [f], + table.hline(end: 2, stroke: red), + [g], [h], [ie], + table.hline(end: 2, stroke: green), +) + +--- grid-stroke-hline-position-bottom --- +// Using position: bottom and position: end without gutter should be the same +// as placing a line after the next track. +#table( + columns: 3, + stroke: blue, + table.hline(end: 2, stroke: red), + table.hline(end: 2, stroke: aqua, position: bottom), + table.vline(end: 2, stroke: green), [a], table.vline(end: 2, stroke: green), [b], table.vline(end: 2, stroke: aqua, position: end), table.vline(end: 2, stroke: green), [c], table.vline(end: 2, stroke: green), + table.hline(end: 2, stroke: 5pt), + [d], [e], [f], + table.hline(end: 2, stroke: red), + [g], [h], [i], + table.hline(end: 2, stroke: red), +) + +--- grid-stroke-vline-position-left-and-right --- +// Test left and right for grid vlines. +#grid( + columns: 3, + inset: 5pt, + grid.vline(stroke: green, position: left), grid.vline(stroke: red, position: right), [a], + grid.vline(stroke: 2pt, position: left), grid.vline(stroke: red, position: right), [b], + grid.vline(stroke: 2pt, position: left), grid.vline(stroke: red, position: right), [c], + grid.vline(stroke: 2pt, position: left) +) + +#grid( + columns: 3, + inset: 5pt, + gutter: 3pt, + grid.vline(stroke: green, position: left), grid.vline(stroke: red, position: right), [a], + grid.vline(stroke: blue, position: left), grid.vline(stroke: red, position: right), [b], + grid.vline(stroke: blue, position: left), grid.vline(stroke: red, position: right), [c], + grid.vline(stroke: 2pt, position: left) +) + +--- table-stroke-vline-position-left-and-right --- +// Test left and right for table vlines. +#table( + columns: 3, + inset: 5pt, + table.vline(stroke: green, position: left), table.vline(stroke: red, position: right), [a], + table.vline(stroke: 2pt, position: left), table.vline(stroke: red, position: right), [b], + table.vline(stroke: 2pt, position: left), table.vline(stroke: red, position: right), [c], + table.vline(stroke: 2pt, position: left) +) + +#table( + columns: 3, + inset: 5pt, + gutter: 3pt, + table.vline(stroke: green, position: left), table.vline(stroke: red, position: right), [a], + table.vline(stroke: blue, position: left), table.vline(stroke: red, position: right), [b], + table.vline(stroke: blue, position: left), table.vline(stroke: red, position: right), [c], + table.vline(stroke: 2pt, position: left) +) + +--- grid-stroke-priority-line-cell --- +// Hlines and vlines should always appear on top of cell strokes. +#table( + columns: 3, + stroke: aqua, + table.vline(stroke: red, position: end), [a], table.vline(stroke: red), [b], [c], + table.cell(stroke: blue)[d], [e], [f], + table.hline(stroke: red), + [g], table.cell(stroke: blue)[h], [i], +) + +#table( + columns: 3, + gutter: 3pt, + stroke: aqua, + table.vline(stroke: red, position: end), [a], table.vline(stroke: red), [b], [c], + table.cell(stroke: blue)[d], [e], [f], + table.hline(stroke: red), + [g], table.cell(stroke: blue)[h], [i], +) + +--- grid-stroke-priority-cell --- +// Ensure cell stroke overrides always appear on top. +#table( + columns: 2, + stroke: black, + table.cell(stroke: red)[a], [b], + [c], [d], +) + +#table( + columns: 2, + table.cell(stroke: red)[a], [b], + [c], [d], +) + +--- grid-stroke-hline-position-bad --- +// Error: 7:3-7:32 cannot place horizontal line at the 'bottom' position of the bottom border (y = 2) +// Hint: 7:3-7:32 set the line's position to 'top' or place it at a smaller 'y' index +#table( + columns: 2, + [a], [b], + [c], [d], + table.hline(stroke: aqua), + table.hline(position: top), + table.hline(position: bottom) +) + +--- grid-stroke-border-partial --- +// Test partial border line overrides +#set page(width: auto, height: 7em, margin: (bottom: 1em)) +#table( + columns: 4, + stroke: (x, y) => if y == 0 or y == 4 { orange } else { aqua }, + table.hline(stroke: blue, start: 1, end: 2), table.cell(stroke: red, v(3em)), table.cell(stroke: blue)[b], table.cell(stroke: green)[c], [M], + [a], [b], [c], [M], + [d], [e], [f], [M], + [g], [h], [i], [M], + table.cell(stroke: red)[a], table.cell(stroke: blue)[b], table.cell(stroke: green)[c], [M], + table.hline(stroke: blue, start: 1, end: 2), +) + +--- grid-stroke-vline-colspan --- +// - Vline should be placed after the colspan. +// - Hline should be placed under the full-width rowspan. +#table( + columns: 3, + rows: 1.25em, + inset: 1pt, + stroke: none, + table.cell(colspan: 2)[a], table.vline(stroke: red), table.hline(stroke: blue), [b], + [c], [d], [e], + table.cell(colspan: 3, rowspan: 2)[a], table.vline(stroke: blue), table.hline(stroke: red) +) + +--- grid-stroke-hline-rowspan --- +// Red line should be above [c] (hline skips the shortest rowspan). +#set text(6pt) +#table( + rows: 1em, + columns: 2, + inset: 1.5pt, + table.cell(rowspan: 3)[a], table.cell(rowspan: 2)[b], + table.hline(stroke: red), + [c] +) + +--- grid-stroke-hline-position-bottom-out-of-bounds --- +// Error: 8:3-8:32 cannot place horizontal line at the 'bottom' position of the bottom border (y = 2) +// Hint: 8:3-8:32 set the line's position to 'top' or place it at a smaller 'y' index +#table( + columns: 2, + gutter: 3pt, + [a], [b], + [c], [d], table.vline(stroke: red), + table.hline(stroke: aqua), + table.hline(position: top), + table.hline(position: bottom) +) + +--- grid-stroke-vline-position-bottom-out-of-bounds --- +// Error: 6:3-6:28 cannot place vertical line at the 'end' position of the end border (x = 2) +// Hint: 6:3-6:28 set the line's position to 'start' or place it at a smaller 'x' index +#grid( + columns: 2, + [a], [b], + grid.vline(stroke: aqua), + grid.vline(position: start), + grid.vline(position: end) +) + +--- grid-stroke-vline-position-bottom-out-of-bounds-gutter --- +// Error: 7:3-7:28 cannot place vertical line at the 'end' position of the end border (x = 2) +// Hint: 7:3-7:28 set the line's position to 'start' or place it at a smaller 'x' index +#grid( + columns: 2, + gutter: 3pt, + [a], [b], + grid.vline(stroke: aqua), + grid.vline(position: start), + grid.vline(position: end) +) + +--- grid-stroke-hline-out-of-bounds --- +// Error: 4:3-4:19 cannot place horizontal line at invalid row 3 +#grid( + [a], + [b], + grid.hline(y: 3) +) + +--- grid-stroke-hline-out-of-bounds-gutter --- +// Error: 5:3-5:19 cannot place horizontal line at invalid row 3 +#grid( + gutter: 3pt, + [a], + [b], + grid.hline(y: 3) +) + +--- grid-stroke-vline-out-of-bounds --- +// Error: 4:3-4:20 cannot place vertical line at invalid column 3 +#table( + columns: 2, + [a], [b], + table.vline(x: 3) +) + +--- grid-stroke-vline-out-of-bounds-gutter --- +// Error: 5:3-5:20 cannot place vertical line at invalid column 3 +#table( + columns: 2, + gutter: 3pt, + [a], [b], + table.vline(x: 3) +) + +--- table-hline-in-grid --- +// Error: 7-20 cannot use `table.hline` as a grid line; use `grid.hline` instead +#grid(table.hline()) + +--- table-vline-in-grid --- +// Error: 7-20 cannot use `table.vline` as a grid line; use `grid.vline` instead +#grid(table.vline()) + +--- grid-hline-in-table --- +// Error: 8-20 cannot use `grid.hline` as a table line; use `table.hline` instead +#table(grid.hline()) + +--- grid-vline-in-table --- +// Error: 8-20 cannot use `grid.vline` as a table line; use `table.vline` instead +#table(grid.vline()) + +--- grid-hline-end-before-start-1 --- +// Error: 3:3-3:31 line cannot end before it starts +#grid( + columns: 3, + grid.hline(start: 2, end: 1), + [a], [b], [c], +) + +--- grid-hline-end-before-start-2 --- +// Error: 3:3-3:32 line cannot end before it starts +#table( + columns: 3, + table.vline(start: 2, end: 1), + [a], [b], [c], + [d], [e], [f], + [g], [h], [i], +) + +--- grid-hline-position-horizon --- +// Error: 24-31 expected `top` or `bottom`, found horizon +#table.hline(position: horizon) + +--- grid-vline-position-center --- +// Error: 24-30 expected `start`, `left`, `right`, or `end`, found center +#table.vline(position: center) + +--- grid-hline-position-right --- +// Error: 24-29 expected `top` or `bottom`, found right +#table.hline(position: right) + +--- grid-vline-position-top --- +// Error: 24-27 expected `start`, `left`, `right`, or `end`, found top +#table.vline(position: top) diff --git a/tests/suite/layout/grid/styling.typ b/tests/suite/layout/grid/styling.typ new file mode 100644 index 00000000..f7cfb97d --- /dev/null +++ b/tests/suite/layout/grid/styling.typ @@ -0,0 +1,160 @@ +// Test grid styling options. + +--- grid-fill-func --- +#set page(height: 70pt) +#set grid(fill: (x, y) => if calc.even(x + y) { rgb("aaa") }) + +#grid( + columns: (1fr,) * 3, + stroke: 2pt + rgb("333"), + [A], [B], [C], [], [], [D \ E \ F \ \ \ G], [H], +) + +--- grid-stroke-none --- +#grid(columns: 3, stroke: none, fill: green, [A], [B], [C]) + +--- grid-align --- +// Test general alignment. +#grid( + columns: 3, + align: left, + [Hello], [Hello], [Hello], + [A], [B], [C], +) + +// Test alignment with a function. +#grid( + columns: 3, + align: (x, y) => (left, center, right).at(x), + [Hello], [Hello], [Hello], + [A], [B], [C], +) + +// Test alignment with array. +#grid( + columns: (1fr, 1fr, 1fr), + align: (left, center, right), + [A], [B], [C] +) + +// Test empty array. +#set align(center) +#grid( + columns: (1fr, 1fr, 1fr), + align: (), + [A], [B], [C] +) + +a + +--- grid-inset --- +// Test inset. +#grid( + columns: (1fr,) * 3, + stroke: 2pt + rgb("333"), + inset: 5pt, + [A], [B], [C], [], [], [D \ E \ F \ \ \ G], [H], +) + +#grid( + columns: 3, + inset: 10pt, + fill: blue, + [A], [B], [C] +) + +#grid( + columns: 3, + inset: (y: 10pt), + [A], [B], [C] +) + +#grid( + columns: 3, + inset: (left: 20pt, rest: 10pt), + stroke: 3pt + red, + [A], [B], [C] +) + +#grid( + columns: 2, + inset: ( + left: 20pt, + right: 5pt, + top: 10pt, + bottom: 3pt, + ), + [A], + [B], +) + +#grid( + columns: 3, + fill: (x, y) => (if y == 0 { aqua } else { orange }).darken(x * 15%), + inset: (x, y) => (left: if x == 0 { 0pt } else { 5pt }, right: if x == 0 { 5pt } else { 0pt }, y: if y == 0 { 0pt } else { 5pt }), + [A], [B], [C], + [A], [B], [C], +) + +#grid( + columns: 3, + inset: (0pt, 5pt, 10pt), + fill: (x, _) => aqua.darken(x * 15%), + [A], [B], [C], +) + +--- grid-inset-folding --- +// Test inset folding +#set grid(inset: 10pt) +#set grid(inset: (left: 0pt)) + +#grid( + fill: red, + inset: (right: 0pt), + grid.cell(inset: (top: 0pt))[a] +) + +--- grid-funcs-gutter --- +// Test interaction with gutters. +#grid( + columns: (3em, 3em), + fill: (x, y) => (red, blue).at(calc.rem(x, 2)), + align: (x, y) => (left, right).at(calc.rem(y, 2)), + [A], [B], + [C], [D], + [E], [F], + [G], [H] +) + +#grid( + columns: (3em, 3em), + fill: (x, y) => (red, blue).at(calc.rem(x, 2)), + align: (x, y) => (left, right).at(calc.rem(y, 2)), + row-gutter: 5pt, + [A], [B], + [C], [D], + [E], [F], + [G], [H] +) + +#grid( + columns: (3em, 3em), + fill: (x, y) => (red, blue).at(calc.rem(x, 2)), + align: (x, y) => (left, right).at(calc.rem(y, 2)), + column-gutter: 5pt, + [A], [B], + [C], [D], + [E], [F], + [G], [H] +) + +#grid( + columns: (3em, 3em), + fill: (x, y) => (red, blue).at(calc.rem(x, 2)), + align: (x, y) => (left, right).at(calc.rem(y, 2)), + gutter: 5pt, + [A], [B], + [C], [D], + [E], [F], + [G], [H] +) |
