pdfkit/docs/table.md
Jake Holland 033ba3426b
Add support for tables (#1577)
* Add page size utilities

- Added page.contentWidth
- Added page.contentHeight

* Add table support

- Tables support cell customization (including colors)
- Tables also support rotatable text (with alignment support)
- Tables have accessibility support

* chore: fix code generation context

- code generation now respects the current document positioning to allow use of page dependent operations

* chore: remove comments from build

* removed unnecessary config optimisations

* Optimize table minification

* Performance improvements to tables

* Improve font handling in tables
2025-02-24 07:49:25 -03:00

378 lines
10 KiB
Markdown

# Tables in PDFKit
## The basics
PDFKit makes adding tables to documents quite simple, and includes many options
to customize the display of the output.
### A simple table
Basic tables can be defined without configuration:
doc.table({
data: [
['Column 1', 'Column 2', 'Column 3'],
['One value goes here', 'Another one here', 'OK?']
]
})
or the more verbose way
doc.table()
.row(['Column 1', 'Column 2', 'Column 3'])
.row(['One value goes here', 'Another one here', 'OK?'])
![1]()
---
### Defining column widths
Tables allow you to define the widths of columns:
* `*` - distributes equally, filling the whole available space (default)
* `fixed value` - a fixed width based on the document content
Example:
doc.table({
columnStyles: [100, "*", 200, "*"],
data: [
["width=100", "star-sized", "width=200", "star-sized"],
[
"fixed-width cells have exactly the specified width",
{ text: "nothing interesting here", textColor: "grey" },
{ text: "nothing interesting here", textColor: "grey" },
{ text: "nothing interesting here", textColor: "grey" }
],
],
});
![2]()
---
### Defining row heights
doc.table({
rowStyles: [20, 50, 70],
data: [
["row 1 with height 20", "column B"],
["row 2 with height 50", "column B"],
["row 3 with height 70", "column B"],
],
});
![3]()
With same height:
doc.table({
rowStyles: 40,
data: [
["row 1", "column B"],
["row 2", "column B"],
["row 3", "column B"],
],
});
![4]()
---
With height from function:
doc.table({
rowStyles: (row) => (row + 1) * 25,
data: [
["row 1", "column B"],
["row 2", "column B"],
["row 3", "column B"],
],
});
![5]()
---
### Column/row spans
Each cell can set a rowSpan or colSpan
doc.table({
columnStyles: [200, "*", "*"],
data: [
[{ colSpan: 2, text: "Header with Colspan = 2" }, "Header 3"],
["Header 1", "Header 2", "Header 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
[
{
rowSpan: 3,
text: "rowspan set to 3\nLorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor",
},
"Sample value 2",
"Sample value 3",
],
["Sample value 2", "Sample value 3"],
["Sample value 2", "Sample value 3"],
[
"Sample value 1",
{
colSpan: 2,
rowSpan: 2,
text: "Both:\nrowspan and colspan\ncan be defined at the same time",
},
],
["Sample value 1"],
],
})
![6]()
---
### Styling
No borders:
doc.table({
rowStyles: { border: false },
data: [
["Header 1", "Header 2", "Header 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
],
})
![7]()
Header line only:
doc.table({
rowStyles: (i) => {
return i < 1 ? { border: [0, 0, 1, 0] } : { border: false };
},
data: [
["Header 1", "Header 2", "Header 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
],
})
![8]()
---
Light Horizontal lines:
doc.table({
rowStyles: (i) => {
return i < 1
? { border: [0, 0, 2, 0], borderColor: "black" }
: { border: [0, 0, 1, 0], borderColor: "#aaa" };
},
data: [
["Header 1", "Header 2", "Header 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
],
})
![9]()
---
But you can provide a custom styler as well
doc.table({
// Set the style for all cells
defaultStyle: { border: 1, borderColor: "gray" },
// Set the style for cells based on their column
columnStyles: (i) => {
if (i === 0) return { border: { left: 2 }, borderColor: { left: "black" } };
if (i === 2) return { border: { right: 2 }, borderColor: { right: "black" } };
},
// Set the style for cells based on their row
rowStyles: (i) => {
if (i === 0) return { border: { top: 2 }, borderColor: { top: "black" } };
if (i === 3) return { border: { bottom: 2 }, borderColor: { bottom: "black" } };
},
data: [
["Header 1", "Header 2", "Header 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
],
})
![10]()
---
Zebra style
doc.table({
rowStyles: (i) => {
if (i % 2 === 0) return { backgroundColor: "#ccc" };
},
data: [
["Sample value 1", "Sample value 2", "Sample value 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
["Sample value 1", "Sample value 2", "Sample value 3"],
],
})
![11]()
---
### Optional border
doc.table({
data: [
[
{ border: [true, false, false, false], backgroundColor: "#eee", text: "border:\n[true, false, false, false]" },
{ border: false, backgroundColor: "#ddd", text: "border:\nfalse" },
{ border: true, backgroundColor: "#eee", text: "border:\ntrue" },
],
[
{ rowSpan: 3, border: true, backgroundColor: "#eef", text: "rowSpan: 3\n\nborder:\ntrue" },
{ border: undefined, backgroundColor: "#eee", text: "border:\nundefined (default)" },
{ border: [false, false, false, true], backgroundColor: "#ddd", text: "border:\n[false, false, false, true]" },
],
[
{ colSpan: 2, border: true, backgroundColor: "#efe", text: "colSpan: 2\n\nborder:\ntrue" },
],
[
{ border: 0, backgroundColor: "#eee", text: "border:\n0 (same as false)" },
{ border: [false, true, true, false], backgroundColor: "#ddd", text: "border:\n[false, true, true, false]" },
],
],
})
![12]()
---
doc.table({
defaultStyle: { border: false, width: 60 },
data: [
["", "column 1", "column 2", "column 3"],
[
"row 1",
{
rowSpan: 3,
colSpan: 3,
border: true,
backgroundColor: "#ccc",
text: "rowSpan: 3\ncolSpan: 3\n\nborder:\n[true, true, true, true]",
},
],
["row 2"],
["row 3"],
],
})
![13]()
---
When defining multiple styles, the cells follow the precedence:
1. `defaultStyle`
2. `columnStyles`
3. `rowStyles`
4. `cellStyle`
so if a table was:
doc.table({
defaultStyle: { border: 1 },
columnStyles: { border: { right: 2 } },
rowStyles: { border: { bottom: 3 } },
data: [
[{ border: { left: 4 } }]
]
})
The resulting cell would have a style of:
{
border: {
top: 1, // From the default
right: 2, // From the column
bottom: 3, // From the row
left: 4 // From the cell
}
}
Internally, PDFKit keeps track of the current X and Y position of table as it
is added to the document. This way, any calls to `text` or `table` will be placed below the table row.
doc
.text('before')
.table({
data: [
['Column 1', 'Column 2', 'Column 3'],
['One value goes here', 'Another one here', 'OK?']
]
})
.text('after')
![16]()
## Table options
- `position` - The position of the table (default `{x: doc.x, y: doc.y}`)
- `maxWidth` - The maximum width the table can expand to (defaults to the remaining content width (offset from the tables position))
- `columnStyles` - Column definitions of the table. (default `auto`)
- `rowStyles` - Row definitions of the table. (default `*`)
- `defaultStyle` - Defaults to apply to every cell
- `data` - The data to render (not required, you can call `.row()`). This can be an iterable (async or sync)
- `debug` - Whether to show the debug lines for all the cells (default `false`)
## Cell options
- `text` - The value, will be cast to a string (`null` and `undefined` are not rendered but the cell is still outlined)
- `rowSpan` - How many rows this cell covers, follows the same logic as HTML `rowspan`
- `colSpan` - How many columns this cell covers, follows the same logic as HTML `colspan`
- `padding` - The padding for the cell (default `0.25em`)
- `border` - The border for the cell (default `1pt`)
- `borderColor` - The border colors for the cell (default `black`)
- `font` - Font options for the cell
- `backgroundColor` - Set the background color of the cell
- `align` - The alignment of the cell text (default `{x: 'left', y: 'top'}`)
- `textStroke` - The text stroke (default `0`)
- `textStrokeColor` - Sets the text stroke color of the cells text (default `black`)
- `textColor` - Sets the text color of the cells text (default `black`)
- `type` - Sets the cell type (for accessibility) (default `TD`)
- `textOptions` - Sets any text options you wish to provide (such as rotation)
- `debug` - Whether to show the debug lines for the cell (default `false`)
## Column options
Extends the [cell options](#cell-options) above with:
- `width` - The width of the column (default `*`)
- `minWidth` - The minimum width of the column (default `0`)
- `maxWidth` - The maximum width of the column (default `Infinity`)
## Row options
Extends the [cell options](#cell-options) above with:
- `height` - The height of the row (default `auto`)
- `minHeight` - The minimum height of the row (default `0`)
- `maxHeight` - The maximum height of the row (default `Infinity`)