Tables
The table model is similar to that of HTML. You combine cells into rows which form a table to be typeset. In HTML for example you can create a simple table with this code:
<table>
<tr>
<td>Hello,</td>
<td>world</td>
</tr>
<tr>
<td>Hello,</td>
<td>user</td>
</tr>
</table>So a table is built row by row. In boxes and glue, the table consists of table rows (TableRow) and cells (TableCell). The contents of each cell is a sequence of Paragraph structs and the contents of a row is a sequence of cells.
Table cell
type TableCell struct {
BackgroundColor *color.Color
BorderTopColor *color.Color
BorderBottomColor *color.Color
BorderLeftColor *color.Color
BorderRightColor *color.Color
Contents []any
BorderTopWidth bag.ScaledPoint
BorderBottomWidth bag.ScaledPoint
BorderLeftWidth bag.ScaledPoint
BorderRightWidth bag.ScaledPoint
CalculatedWidth bag.ScaledPoint
CalculatedHeight bag.ScaledPoint
// SpecifiedWidth is the cell's declared width (CSS `width` on a
// <td>/<th>, percentages already resolved against the table width).
// Zero means "not specified". CSS 2.1 §17.5.2.2 folds it into the
// automatic layout as a lower bound for the column, so a cell can
// widen its column beyond the content but never clips content that
// needs more room. Cells spanning several columns are ignored.
SpecifiedWidth bag.ScaledPoint
// MinHeight is the cell's declared height (CSS `height` on a
// <td>/<th>), measured on the outside of the cell including padding
// and borders. Like the row's MinHeight it is a lower bound only
// (CSS 2.1 §17.5.3), zero means "not specified".
MinHeight bag.ScaledPoint
HAlign HorizontalAlignment
VAlign VerticalAlignment
ExtraColspan int
ExtraRowspan int
PaddingTop bag.ScaledPoint
PaddingBottom bag.ScaledPoint
PaddingLeft bag.ScaledPoint
PaddingRight bag.ScaledPoint
IsHeader bool // true for <th> cells
// ID is the source element's id, copied onto the vlist the cell builds.
ID string
}Table row
type TableRow struct {
// ID is the source element's id, copied onto the hlist the row builds.
ID string
Cells []*TableCell
CalculatedHeight bag.ScaledPoint
// MinHeight is the declared height of the row (CSS `height` on a
// <tr>). CSS 2.1 §17.5.3 treats it as a lower bound: the row grows
// to fit its cells but never shrinks below it.
MinHeight bag.ScaledPoint
// FixedHeight, when not zero, is the height of the row whatever its
// cells hold (Word's "exact" row height), and MinHeight on the row and
// its cells is ignored. Content that does not fit draws past the row in
// the direction its VAlign leaves open, while background and borders
// keep the row height. Rows are drawn in order, so content that runs
// into the next row is painted over by that row's background. A row
// with a fixed height never breaks inside, and a rowspan's missing
// height goes to the rows of the span that are not fixed.
FixedHeight bag.ScaledPoint
VAlign VerticalAlignment
// BreakInside lets the row break across pages or frames: BuildTable
// gives its hlist a RowSplitter in Attributes["_split"]. A row a rowspan
// reaches into or out of stays whole. When a nested table in the row
// breaks, its header rows are not repeated in the rest.
BreakInside bool
}type Table struct {
FontFamily *FontFamily
Rows TableRows
ColSpec []ColSpec
// BorderModel is how the borders of neighbouring cells meet. The zero
// value is BorderModelCollapse: the border between two cells is drawn
// once, as wide as the wider of the two, and the table's own border
// (BorderTopWidth and friends) is merged into the cells on the edge.
// BorderModelSeparate draws every border of every cell and keeps the
// cells apart by BorderSpacingHorizontal and BorderSpacingVertical,
// also between the outer cells and the edge of the table; the table's
// own border is then not drawn here but left to the caller, who knows
// the table's padding and background.
BorderModel BorderModel
BorderSpacingHorizontal bag.ScaledPoint
BorderSpacingVertical bag.ScaledPoint
BorderTopWidth bag.ScaledPoint
BorderRightWidth bag.ScaledPoint
BorderBottomWidth bag.ScaledPoint
BorderLeftWidth bag.ScaledPoint
BorderTopColor *color.Color
BorderRightColor *color.Color
BorderBottomColor *color.Color
BorderLeftColor *color.Color
MaxWidth bag.ScaledPoint
FontSize bag.ScaledPoint
Leading bag.ScaledPoint
Stretch bool
HeaderRows int // number of initial rows that are header rows (from <thead>)
FooterRows int // number of trailing rows that are footer rows (from <tfoot>)
}HeaderRows specifies how many of the initial rows are header rows (typically from <thead>). When a table spans multiple pages, these rows are automatically repeated at the top of each continuation page. The header repetition is handled by the page breaker (e.g. htmlbag’s outputGroupNodes): BuildTable stores a closure in the VList’s attributes that can rebuild the header rows fresh for each new page.
Stretch controls whether the table expands to fill MaxWidth. When false, the table is only as wide as its content requires.
func (fe *Document) BuildTable(tbl *Table) ([]*node.VList, error)BuildTable returns a single VList containing all table rows. If HeaderRows is set, the VList carries metadata that enables the page breaker to split the table across pages and repeat headers automatically. The caller does not need to handle splitting: the page breaker does it during page output.
Rows that break inside
A row is set as one piece. A page breaker moves it to the next page whole, and a row taller than the page runs past its foot. A row with BreakInside set may break inside instead: BuildTable gives its hlist a RowSplitter in Attributes["_split"], which the page breaker calls with the room that is left.
type RowSplitter func(avail bag.ScaledPoint) (first, rest *node.HList, ok bool)first is the part of the row that fits, rest what is left of every cell. The rest has a splitter of its own, so a row can run over as many pages as it needs. ok is false when not even one line of the row fits, and the page breaker moves the row as it would any other. Calling the splitter leaves the row as it is, so a page breaker can try a break and then choose another one.
The cells break between their lines, between paragraphs and between the rows of a nested table, and inside a nested row that may break as well, but never between the rows a rowspan joins. Both parts keep the cells’ borders, padding and background, every cell as tall as the tallest, and the rest draws the line above the row again.
Some rows stay whole even with BreakInside: a row that a rowspan reaches into or out of, since the spanning cell is drawn with its first row. A list packed to a fixed height is not broken either, and the header rows of a nested table are not repeated after a break.